diff --git a/app/helpers/image_helper.rb b/app/helpers/image_helper.rb
new file mode 100644
index 0000000..9b6afd7
--- /dev/null
+++ b/app/helpers/image_helper.rb
@@ -0,0 +1,16 @@
+module ImageHelper
+ # 画像圧縮オプション(品質85%で視覚的な劣化なく30-50%削減)
+ IMAGE_COMPRESSION_OPTIONS = { saver: { quality: 85 } }.freeze
+
+ # variantオプションに圧縮設定をマージ
+ #
+ # @param options [Hash] resize_to_fillなどのvariantオプション
+ # @return [Hash] 圧縮設定がマージされたオプション
+ #
+ # @example
+ # compressed_variant(resize_to_fill: [450, 300])
+ # # => { resize_to_fill: [450, 300], saver: { quality: 85 } }
+ def compressed_variant(options)
+ options.merge(IMAGE_COMPRESSION_OPTIONS)
+ end
+end
diff --git a/app/views/shared/_membership_avatar.html.erb b/app/views/shared/_membership_avatar.html.erb
index ce739af..af5d43e 100644
--- a/app/views/shared/_membership_avatar.html.erb
+++ b/app/views/shared/_membership_avatar.html.erb
@@ -1,9 +1,10 @@
<% size ||= 60 %>
<% initial_font_size ||= 'sm' %>
+<% lazy ||= true %>
<% if membership.avatar.attached? %>
- <%= image_tag membership.avatar.variant(resize_to_fill: [size, size]), alt: "#{membership.display_name}のアバター", class: "w-#{size/4} h-#{size/4} rounded-full object-cover" %>
+ <%= image_tag membership.avatar.variant(compressed_variant(resize_to_fill: [size, size])), alt: "#{membership.display_name}のアバター", class: "w-#{size/4} h-#{size/4} rounded-full object-cover", loading: lazy ? "lazy" : "eager" %>
<% else %>
diff --git a/app/views/teams/events/_main_column.html.erb b/app/views/teams/events/_main_column.html.erb
index 9c081dd..b171714 100644
--- a/app/views/teams/events/_main_column.html.erb
+++ b/app/views/teams/events/_main_column.html.erb
@@ -25,7 +25,7 @@
<% if event.image.attached? %>
- <%= image_tag event.image.variant(resize_to_fill: [1500, 1000]), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover" %>
+ <%= image_tag event.image.variant(compressed_variant(resize_to_fill: [1500, 1000])), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover" %>
<% else %>
@@ -127,7 +127,7 @@
<% if event.report_image.attached? %>
- <%= image_tag event.report_image.variant(resize_to_fill: [1500, 1000]), alt: "#{event.name}の開催レポート画像", class: "w-full h-full object-cover" %>
+ <%= image_tag event.report_image.variant(compressed_variant(resize_to_fill: [1500, 1000])), alt: "#{event.name}の開催レポート画像", class: "w-full h-full object-cover", loading: "lazy" %>
<% end %>
diff --git a/app/views/teams/memberships/_event_item.html.erb b/app/views/teams/memberships/_event_item.html.erb
index 5b88b4d..e423382 100644
--- a/app/views/teams/memberships/_event_item.html.erb
+++ b/app/views/teams/memberships/_event_item.html.erb
@@ -1,7 +1,7 @@
<%= link_to team_event_path(@team, event), class: "flex gap-4 py-4 border-t border-gray-200" do %>
<% if event.image.attached? %>
- <%= image_tag event.image.variant(resize_to_fill: [150, 100]), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover" %>
+ <%= image_tag event.image.variant(compressed_variant(resize_to_fill: [150, 100])), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover", loading: "lazy" %>
<% else %>
No Image
diff --git a/app/views/teams/show.html.erb b/app/views/teams/show.html.erb
index f8b37cc..551de18 100644
--- a/app/views/teams/show.html.erb
+++ b/app/views/teams/show.html.erb
@@ -16,7 +16,7 @@
<% if event.image.attached? %>
- <%= image_tag event.image.variant(resize_to_fill: [450, 300]), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover" %>
+ <%= image_tag event.image.variant(compressed_variant(resize_to_fill: [450, 300])), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover", loading: "lazy" %>
<% else %>
@@ -167,7 +167,7 @@
<% if event.image.attached? %>
- <%= image_tag event.image.variant(resize_to_fill: [450, 300]), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover" %>
+ <%= image_tag event.image.variant(compressed_variant(resize_to_fill: [450, 300])), alt: "#{event.name}のイメージ画像", class: "w-full h-full object-cover", loading: "lazy" %>
<% else %>
diff --git a/spec/helpers/image_helper_spec.rb b/spec/helpers/image_helper_spec.rb
new file mode 100644
index 0000000..b24e926
--- /dev/null
+++ b/spec/helpers/image_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'rails_helper'
+
+RSpec.describe ImageHelper, type: :helper do
+ describe '#compressed_variant' do
+ it '圧縮オプションをマージして返す' do
+ result = helper.compressed_variant(resize_to_fill: [ 450, 300 ])
+ expect(result).to eq(resize_to_fill: [ 450, 300 ], saver: { quality: 85 })
+ end
+
+ it '既存のオプションを保持しつつ圧縮オプションを追加する' do
+ result = helper.compressed_variant(resize_to_limit: [ 1000, 1000 ], format: :webp)
+ expect(result).to eq(resize_to_limit: [ 1000, 1000 ], format: :webp, saver: { quality: 85 })
+ end
+
+ it '空のハッシュにも圧縮オプションを追加する' do
+ result = helper.compressed_variant({})
+ expect(result).to eq(saver: { quality: 85 })
+ end
+ end
+end