loading=”lazy” を全画像に付けるとLCPは悪化する|外す画像の見分け方とfetchpriority・CLS対策まで

loading=”lazy” を全画像に付けるとLCPは悪化する|外す画像の見分け方とfetchpriority・CLS対策まで

「画像を全部 loading="lazy" にしたのにPageSpeedのスコアが上がらない」「むしろ表示が遅くなった気がする」——表示速度の改善で最初に踏みやすい罠です。結論から言うと、遅延読み込みは「画面に最初から映らない画像」だけに効く施策で、ファーストビューに入る画像に付けると読み込みの開始そのものが後ろへずれ、LCP(最大コンテンツの描画)は確実に遅くなります。この記事では、LCP要素を機械的に特定する手順、WordPressが自動で付ける lazy を先頭だけ外す方法、fetchpriority の使いどころ、そしてCLSを同時に潰す書き方までを、実案件でやっている順番のまま整理します。

なぜ「全画像lazy」で遅くなるのか

遅延読み込みは、ブラウザに「この画像は今すぐ要らない」と伝える指示です。ブラウザはHTMLを読んだ時点で画像のダウンロードを始めますが、loading="lazy" が付いていると、レイアウトが確定して「その画像がビューポートに近づいた」と判定されるまで開始しません。

ファーストビューのメイン画像は、たいていそのページのLCP要素です。つまりページの表示完了時刻を決めている画像に、わざわざ「後回しでいい」と書いている状態になります。実案件(WordPress・lionmedia系テーマ・記事上部にアイキャッチが出るレイアウト)で、アイキャッチの lazy を外して fetchpriority="high" を付けただけで、モバイル回線相当のスロットリング下でLCPが約2.4秒から約1.6秒まで縮みました。CSSもJSも触っていません。属性2つの差です。

逆に、記事本文の中盤から下にある解説図やギャラリーは lazy が正しく効きます。差が出るのは「最初の1〜2枚をどう扱うか」だけで、そこだけが勝負どころです。

まず自分のサイトのLCP要素を特定する

画像をいじる前に、そのページのLCPが本当に画像なのかを確認します。ヒーローにテキストを大きく置いているデザインだと、LCP要素が h1 や見出しのテキストブロックであることは珍しくありません。その場合は画像を最適化しても数値は動かず、フォント読み込みの方が効きます。

PageSpeed Insightsの「最大コンテンツの描画要素」でも分かりますが、実機やログイン後の画面で確認したいときは、コンソールに次を貼るのが早いです。

// このページのLCP要素をコンソールに出す(読み込み直後に実行)
new PerformanceObserver(function (list) {
  var e = list.getEntries().pop();
  console.log('LCP:', Math.round(e.startTime), 'ms');
  console.log('element:', e.element);
  console.log('url:', e.url || '(text)');
  if (e.element) e.element.style.outline = '3px solid magenta';
}).observe({ type: 'largest-contentful-paint', buffered: true });

実行すると、LCPになっている要素に枠が付き、それが画像なら url にファイルパスが出ます。ここで (text) と表示されたら、この記事の対象外です。フォントの font-display や先読みを見直してください。

注意点として、LCP要素はビューポート幅で変わります。PCではヒーロー画像、スマホでは見出しテキストがLCPになる、という分岐は普通に起きるので、必ずスマホ幅でも同じ計測をしてから施策を決めます。

WordPressは勝手にlazyを付けている

WordPressは5.5以降、本文とアイキャッチの img に loading="lazy" を自動で付与します。テーマのテンプレートに書いていなくても付くため、「うちはlazyを設定していないから関係ない」は成立しません。実際、テーマ側のアイキャッチ出力に直撃していたケースを何度も見ています。

まずブラウザで実際のHTMLを確認します。開発者ツールのElementsではなく、view-source: で生のHTMLを見るのが確実です(JSで書き換えている場合に区別が付きます)。

先頭の数枚だけ自動付与を止めるには、functions.php(子テーマ)に次を書きます。WordPress 5.9以降で使えるフィルタです。

// 先頭2枚の画像には lazy を付けない(3枚目以降は従来どおり遅延)
add_filter( 'wp_omit_loading_attr_threshold', function () {
    return 2;
} );

この「2」はファーストビューに入る画像の枚数に合わせます。ロゴが img で出力されているテーマだと、ロゴが1枚目を消費してアイキャッチが2枚目になるため、1では足りません。実測で、ロゴ+アイキャッチ構成のサイトは2、ロゴがSVGインラインまたはCSS背景のサイトは1が適正でした。ここは推測せず、生HTMLで img の登場順を数えて決めます。

アイキャッチだけを狙って外す

枚数で切ると意図しない画像まで外れる場合は、アイキャッチの出力に絞って属性を上書きします。

// アイキャッチだけ lazy を外し、優先度を上げる
add_filter( 'wp_get_attachment_image_attributes', function ( $attr, $attachment, $size ) {
    if ( is_singular() && has_post_thumbnail() && get_post_thumbnail_id() === $attachment->ID ) {
        $attr['loading']       = 'eager';
        $attr['fetchpriority'] = 'high';
        $attr['decoding']      = 'sync';
    }
    return $attr;
}, 10, 3 );

注意したいのは、この関数はアーカイブページの一覧サムネイルでも走る点です。is_singular() を外すと一覧の全サムネイルが eager になり、今度は一覧ページが重くなります。条件を必ず付けてください。

fetchpriority=”high” は1ページ1枚だけ

fetchpriority="high" は「他より先に取ってこい」という相対的な指示です。複数の画像に付けると優先度が横並びになり、実質的に何も指定していないのと同じになります。効かせたい画像を1枚に絞るのが鉄則です。

<!-- ファーストビューのメイン画像(1ページに1つだけ) -->
<img src="/img/hero.webp" width="1200" height="630" alt="…"
     fetchpriority="high" decoding="sync">

<!-- 本文中盤以降の図版 -->
<img src="/img/fig-01.webp" width="800" height="450" alt="…"
     loading="lazy" decoding="async">

スライダー(Swiperなど)を置いている場合は、1枚目だけ eager + high、2枚目以降は lazy にします。スライダープラグインの設定で「全スライドを遅延」にしていると1枚目まで遅れるので、プラグイン側の設定も合わせて確認します。

srcset・pictureを使っている場合の先読み

レスポンシブ画像でさらに前倒ししたいときは <head> で先読みします。href だけを書くと srcset と食い違って二重ダウンロードになるため、imagesrcset と imagesizes を必ずセットで書きます。

<link rel="preload" as="image"
      href="/img/hero-1200.webp"
      imagesrcset="/img/hero-800.webp 800w, /img/hero-1200.webp 1200w"
      imagesizes="(max-width: 767px) 100vw, 1200px"
      fetchpriority="high">

preloadは「効くが副作用がある」施策です。全ページ共通の <head> に固定パスで書くと、その画像を使っていないページでも無駄にダウンロードします。トップページなど画像が固定されているテンプレートに限定して入れてください。

lazyが効かない・逆効果になるケース

属性を付けたのに挙動が変わらないときは、たいてい次のどれかです。

  • CSSの背景画像には効かない。background-image は loading 属性の対象外です。ヒーローを背景画像で組んでいる場合、遅延も優先度指定もHTMLからは効きません。速度を詰めるなら img か picture に置き換えるのが先です。
  • display:none の中にある画像。ハンバーガーメニューの中身などは、lazyだと開くまで読み込まれず、開いた瞬間に空白が出ます。メニュー内のロゴなど小さく確実に見せたい画像は eager のままにします。
  • 画像が小さすぎる。数KBのアイコンに lazy を付けても短縮効果はほぼ無く、リクエストの発生タイミングがばらけるだけです。目安として、20KB未満の装飾画像は lazy の対象から外して考えて構いません。
  • プラグインの多重付与。高速化系プラグインが独自のlazyload(data-src へ退避してJSで差し替える方式)を持っていると、WordPress標準の loading 属性と二重にかかり、fetchpriority を書いても src が空のままなので効きません。この方式のプラグインを使う場合は、プラグイン側の除外設定(先頭N枚を除外/特定クラスを除外)で対応します。両方で制御しようとすると必ず矛盾します。

同時にCLSを潰す:width/heightとaspect-ratio

遅延読み込みを入れると、画像が後から挿し込まれる分だけレイアウトシフト(CLS)が起きやすくなります。読み込み前に高さが確保されていないと、画像が来た瞬間に下のテキストが押し下げられ、読んでいる最中に文章が動きます。表示速度の数値以前に、体感でいちばん嫌われる挙動です。

対策はシンプルで、すべての img に元画像の width と height を書くことです。CSSで width:100% にしていても属性は残します。ブラウザは属性から縦横比を計算して、読み込み前に場所を確保します。

<img src="/img/fig-01.webp" width="800" height="450" alt="…" loading="lazy">

ここで頻発する事故が、CSS側の書き忘れです。

/* NG:属性のheightがそのまま効いて縦が潰れる/伸びる */
img { width: 100%; }

/* OK:比率を保って可変させる */
img {
  width: 100%;
  height: auto;
}

height:auto が無いと、幅だけ100%に伸びて高さは属性値(例では450px)で固定され、画像が縦に潰れます。「属性を書いたらデザインが崩れた」という相談はほぼこれが原因です。リセットCSSに img{max-width:100%;height:auto;} が入っているかを先に確認してください。

サイズが事前に分からない画像はaspect-ratio

外部APIから引っ張る画像やユーザー投稿など、縦横が事前に確定しない箇所は、枠側で比率を固定します。

.c-thumb {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}
.c-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

この形なら、画像が来る前から枠の高さが決まるためシフトしません。object-fit:cover は縦横比が違う画像をトリミング表示するので、人物写真では顔が切れることがあります。その場合は object-position: top; を足すと、証明写真的な素材ではほぼ収まります。

なお、幅を固定しないまま画像を並べていて横スクロールが出てしまう場合は、原因が画像とは限りません。特定手順はスマホで横スクロールが出る原因の特定手順にまとめてあります。

作業の順番と公開前チェックリスト

手を付ける順番を間違えると、計測が濁って何が効いたのか分からなくなります。実務では次の順で固定しています。

  • ①LCP要素を特定する(PC幅・スマホ幅の両方)。画像でなければ画像施策はやらない。
  • ②その1枚だけ lazy を外す。ここで一度計測して差を見る。
  • ③fetchpriority=”high” を足す。再計測。効果が出るのはたいていこの2ステップまで。
  • ④preloadは最後。テンプレートが固定のページに限る。
  • ⑤width/heightとheight:autoを全画像に通す。CLS側の仕上げ。

公開前には次を確認します。

  • 生HTML(view-source:)で fetchpriority="high" が1つだけであること
  • ファーストビューに映る画像に loading="lazy" が残っていないこと
  • 一覧・アーカイブページのサムネイルが eager になっていないこと
  • 全 img に width と height があり、CSSに height:auto があること
  • スマホ幅で上から下までスクロールし、文章が押し下げられる箇所が無いこと

表示速度は、重い施策を1つ入れるより属性2つを正しい画像に付けるほうが効くことが多い領域です。まずLCP要素の特定から始めてください。サーバー側の応答時間まで含めて詰めたい場合は、Web制作者が選ぶレンタルサーバー比較も併せて確認すると原因の切り分けが速くなります。多言語サイトのように画像とページ数が増える構成では、この差がそのまま離脱率に出ます(多言語サイトの作り方)。

多言語・ベトナム語サイトの制作でお困りではありませんか?

フォント・文字化け・WordPress多言語化・サーバー選定まで、多言語サイト制作の実績を持つ制作会社が一括対応します。お見積り・ご相談は無料です。

多言語サイト制作の詳細を見る →

すぐに相談したい方は お問い合わせフォームへ

関連記事(コーディング実務Tips)

Web制作Tips(コーディング実務)カテゴリの最新記事