JA EN

画像読み込み戦略の設計 - preload, fetchpriority, decoding を使いこなす

· 約 9 分で読めます

画像読み込みの優先度制御が必要な理由 - ブラウザの限界を理解する

ブラウザは HTML を解析する際、プリロードスキャナーと呼ばれる仕組みで <img> タグを先読みし、画像のダウンロードを開始します。しかし、この自動的な優先度付けには限界があります。ブラウザはページのレイアウトが確定するまで、どの画像がビューポート内に表示されるか (つまり LCP 候補か) を正確に判断できません。

Chrome のリソース優先度は 5 段階 (Highest, High, Medium, Low, Lowest) で管理されています。デフォルトでは、ビューポート内の画像は High、ビューポート外の画像は Low に分類されます。しかし、レイアウト計算が完了するまでこの判定は行われないため、初期段階では全画像が Medium として扱われ、最適な優先度付けが遅延します。

この問題は特に以下のケースで顕著になります。CSS 背景画像は HTML のプリロードスキャナーが検出できないため、CSS の解析完了まで発見されません。JavaScript で動的に挿入される画像も同様です。また、<picture> 要素内の画像は、メディアクエリの評価後に初めてダウンロード対象が確定するため、単純な <img> より発見が遅れます。

これらの課題に対して、開発者が明示的に優先度を指示する手段が preloadfetchprioritydecoding の 3 つの属性です。それぞれ異なるレイヤーで画像の読み込みを最適化し、組み合わせることで LCP を 500ms-1 秒以上改善できるケースがあります。

preload - 画像の早期発見を実現するリソースヒント

<link rel="preload"> は、ブラウザに「このリソースはすぐに必要になる」と事前に通知するリソースヒントです。HTML の <head> セクションに記述することで、DOM 解析の最初期段階でリソースの取得を開始させます。

画像の preload が特に効果的なケースは 3 つあります。第一に、CSS の background-image で指定された画像です。通常、CSS ファイルのダウンロード → 解析 → CSSOM 構築 → レンダーツリー構築という一連のプロセスが完了するまで画像の存在が認識されません。preload を使えば、この待ち時間を完全にスキップできます。

第二に、JavaScript で動的に生成される画像です。SPA (Single Page Application) のヒーロー画像や、カルーセルの最初の画像がこれに該当します。第三に、<picture> 要素で条件分岐する画像です。

実装例: <link rel="preload" as="image" href="/images/hero.avif" type="image/avif" fetchpriority="high">

レスポンシブ画像の preload には imagesrcsetimagesizes 属性を使用します: <link rel="preload" as="image" imagesrcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w" imagesizes="100vw" type="image/avif">

注意点: preload は強力ですが、乱用すると逆効果です。preload されたリソースは最優先でダウンロードされるため、CSS や JavaScript など他の重要リソースの帯域を奪います。原則として、LCP 画像 1 枚のみに限定してください。Chrome DevTools の Console に「preload されたリソースが 3 秒以内に使用されなかった」という警告が出る場合は、不要な preload を削除すべきです。

fetchpriority - リソース優先度の明示的な制御

fetchpriority 属性は、同じ種類のリソース間での相対的な優先度をブラウザに伝えます。値は highlowauto (デフォルト) の 3 つです。preload が「いつ発見するか」を制御するのに対し、fetchpriority は「発見後にどの順番で取得するか」を制御します。

fetchpriority="high" の効果は、Chrome の内部優先度を Medium から High に引き上げることです。これにより、画像のダウンロードが CSS や同期スクリプトと同等の優先度で実行されます。LCP 画像に適用した場合、実測で 100-400ms の LCP 改善が報告されています。

具体的な使い分け:

実装例: <img src="hero.jpg" fetchpriority="high" alt="メインビジュアル" width="1200" height="600">

fetchpriority<img><link rel="preload"><script><iframe> に適用可能です。画像以外のリソースにも使えるため、ページ全体のリソース優先度を統合的に設計できます。例えば、ファーストビューに不要な第三者スクリプトに fetchpriority="low" を設定し、LCP 画像の帯域を確保する戦略が有効です。

decoding 属性 - 画像デコードとメインスレッドの関係

decoding 属性は、画像のデコード処理 (圧縮データからピクセルデータへの変換) をメインスレッドで同期的に行うか、非同期的に行うかを制御します。値は syncasyncauto (デフォルト) の 3 つです。

decoding="async" を指定すると、画像のデコードがメインスレッドをブロックしなくなります。これにより、画像のデコード中も DOM の構築やスクリプトの実行が継続され、ページ全体のレンダリングが高速化します。特に大きな画像 (2,000 px 以上) や、複数の画像が同時にデコードされる場面で効果が顕著です。

decoding="sync" は、画像のデコードが完了するまで後続のレンダリングをブロックします。これは、画像が表示される瞬間にデコード済みであることを保証したい場合に使用します。例えば、ページ遷移時のヒーロー画像で「画像が一瞬ぼやけて表示される」現象を防ぎたい場合です。ただし、メインスレッドのブロックは INP (Interaction to Next Paint) に悪影響を与えるため、慎重に使用してください。

実用的な指針として、ほとんどの画像には decoding="async" を推奨します。ブラウザのデフォルト (auto) は多くの場合 async と同等の動作をしますが、明示的に指定することで意図を明確にし、ブラウザ間の挙動差を排除できます。

注意すべき点として、decoding="async" は画像の「ダウンロード」には影響しません。あくまで「ダウンロード完了後のデコード処理」の実行方法を制御するだけです。ダウンロードの優先度を制御したい場合は fetchpriority を、ダウンロードの開始タイミングを制御したい場合は preloadloading を使用します。

3 つの属性の組み合わせ - LCP 最適化の実践パターン

preload、fetchpriority、decoding は互いに補完する関係にあり、適切に組み合わせることで最大の効果を発揮します。代表的なパターンを紹介します。

パターン 1: CSS 背景画像の LCP 最適化

<head><link rel="preload" as="image" href="hero-bg.avif" type="image/avif" fetchpriority="high"> を記述し、対応する要素に background-image を設定します。preload で早期発見 + fetchpriority で最優先取得を実現します。

パターン 2: ファーストビューの img 要素

<img src="hero.jpg" fetchpriority="high" decoding="async" loading="eager" width="1200" height="600" alt="..."> - fetchpriority で優先度を上げ、decoding="async" でメインスレッドのブロックを防ぎ、loading="eager" (デフォルト) で遅延読み込みを無効化します。

パターン 3: ビューポート外の画像

<img src="below-fold.jpg" loading="lazy" fetchpriority="low" decoding="async" width="800" height="400" alt="..."> - loading="lazy" でビューポート進入まで読み込みを遅延し、fetchpriority="low" で他の画像との帯域競合を回避します。

パターン 4: カルーセルの最初の画像

最初のスライドのみ fetchpriority="high" + loading="eager"、2 枚目以降は fetchpriority="low" + loading="lazy" を設定します。ユーザーが最初に目にする画像だけを優先的に読み込み、残りはインタラクション後に取得します。

これらのパターンを組み合わせた実測結果として、EC サイトの商品一覧ページで LCP が 3.2 秒から 1.8 秒に改善 (44% 短縮) した事例があります。

実装時の注意点とデバッグ方法

画像読み込み戦略の実装で陥りがちなミスと、その検証方法を解説します。

よくあるミス 1: LCP 画像に loading="lazy" を設定 - これは最も頻繁に見られるパフォーマンス劣化の原因です。loading="lazy" は画像がビューポートに近づくまでダウンロードを遅延させるため、LCP 画像に適用すると 300-500ms の遅延が発生します。Chrome DevTools の Performance パネルで LCP 要素を特定し、その画像に lazy が付いていないか確認してください。

よくあるミス 2: preload の過剰使用 - 3 枚以上の画像を preload すると、帯域の奪い合いが発生し、かえって全体が遅くなります。preload は LCP 画像 1 枚に限定するのが原則です。Console に「The resource was preloaded using link preload but not used within a few seconds」の警告が出ていないか確認します。

よくあるミス 3: fetchpriority="high" の乱用 - 全画像に high を設定すると、優先度の差別化が無意味になります。ページあたり 1-2 枚に限定してください。

デバッグ方法: Chrome DevTools の Network パネルで Priority 列を表示し、各画像の実際の優先度を確認します。Waterfall チャートで画像のダウンロード開始タイミングと完了タイミングを視覚的に確認し、LCP 画像が最初にダウンロード完了しているか検証します。Lighthouse の「Largest Contentful Paint element」セクションで LCP 要素と改善提案を確認し、WebPageTest の filmstrip view で実際のレンダリング過程を 100ms 単位で追跡します。

計測ツール: web-vitals ライブラリを使用して RUM データを収集し、fetchpriority 導入前後の LCP 分布を比較します。p75 値で 200ms 以上の改善が確認できれば、施策は成功と判断できます。

関連記事

画像の遅延読み込み実装ガイド - loading=lazy と IntersectionObserver の使い分け

Web ページの初期表示速度を改善する画像遅延読み込みの実装方法を、ネイティブ API と JavaScript の両アプローチで解説します。

Web サイトの画像パフォーマンス監査 - Core Web Vitals 改善の実践ガイド

Web サイトの画像がパフォーマンスに与える影響を監査する方法を解説。LCP 改善、CLS 防止、転送量削減の具体的な手法を紹介します。

Core Web Vitals と画像最適化の関係 - LCP, CLS, INP を改善する実践手法

Core Web Vitals の 3 指標 (LCP, CLS, INP) に画像が与える影響と、具体的な改善手法を解説。実測データに基づくパフォーマンス改善の優先順位と実装パターンを紹介します。

Web 画像最適化チェックリスト - 実務で使える 15 項目を徹底解説

Web サイトの画像最適化に必要な 15 のチェック項目を実務視点で解説。Core Web Vitals 改善に直結する具体的な施策と優先順位を紹介します。

画像 SEO の完全ガイド - alt 属性、ファイル名、サイズ最適化で検索流入を増やす

画像検索からの流入を最大化する SEO テクニックを網羅。alt 属性の書き方、ファイル名の命名規則、構造化データ、Core Web Vitals 対策まで実践的に解説します。

レスポンシブ画像の実装ガイド - srcset, sizes, picture 要素の完全解説

デバイスの画面サイズや解像度に応じて最適な画像を配信するレスポンシブ画像の実装方法を、コード例とともに詳しく解説します。

関連用語