JA EN

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

· 約 9 分で読めます

遅延読み込みの基本概念とパフォーマンス効果

画像の遅延読み込み (Lazy Loading) とは、ユーザーのビューポートに画像が近づいたタイミングで初めてダウンロードを開始する技術です。ページ内に 20 枚の画像がある場合、初期表示時にはファーストビューの 2〜3 枚だけを読み込み、残りはスクロールに応じて順次読み込みます。これにより、初期ページロードに必要なネットワークリソースを大幅に削減できます。

パフォーマンスへの効果は劇的です。画像が多いページでは、初期ロード時のネットワークリクエスト数を 70〜80% 削減できます。これにより、ブラウザのネットワーク帯域が HTML、CSS、JavaScript など重要なリソースに集中し、Time to Interactive (TTI) が大幅に改善されます。実測データでは、20 枚の画像を含むページで遅延読み込みを導入した結果、初期ページロード時間が 3.2 秒から 1.4 秒に短縮された事例があります。

ただし、遅延読み込みは万能ではありません。ファーストビュー内の画像に適用すると、逆に LCP が悪化します。ユーザーが最初に目にする画像は即座に読み込む必要があるため、遅延読み込みの対象はファーストビュー外の画像に限定すべきです。この判断を誤ると、Core Web Vitals のスコアが低下し、SEO に悪影響を及ぼします。

ネイティブ loading 属性による実装

HTML の loading 属性は、ブラウザネイティブの遅延読み込み機能を提供します。2024 年時点で主要ブラウザすべてがサポートしており、JavaScript なしで遅延読み込みを実現できる最もシンプルな方法です。

使用方法は極めて簡単です:

loading 属性には 3 つの値があります。lazy はビューポートに近づくまで読み込みを遅延させます。eager は即座に読み込みを開始します (デフォルト動作)。auto はブラウザに判断を委ねます。

ネイティブ遅延読み込みの重要な特性として、ブラウザが「どの程度ビューポートに近づいたら読み込みを開始するか」の閾値を自動的に決定する点があります。Chrome の場合、接続速度に応じて閾値が動的に変化します。高速接続では約 1,250 px 手前、低速接続 (3G 相当) では約 2,500 px 手前から読み込みを開始します。この適応的な動作により、ユーザーがスクロールした際に画像が表示されるまでの待ち時間を最小化しています。

必須の注意点: loading="lazy" を使用する場合、widthheight 属性を必ず指定してください。これらがないと、ブラウザは画像のサイズを事前に把握できず、レイアウトシフト (CLS) が発生します。

IntersectionObserver による高度な制御

IntersectionObserver API を使用すると、ネイティブ loading 属性では実現できない細かな制御が可能になります。読み込み開始の閾値、アニメーション効果、プレースホルダーの管理など、UX を細部まで作り込みたい場合に適しています。

基本的な実装パターン:

rootMargin オプションで読み込み開始の閾値を制御できます。'200px 0px' と指定すると、ビューポートの上下 200px 手前に画像が入った時点で読み込みが開始されます。この値を大きくするとスクロール時の画像表示がスムーズになりますが、初期リクエスト数が増加するトレードオフがあります。

threshold オプションを使用すると、画像がどの程度ビューポートに入ったかの割合で発火タイミングを制御できます。threshold: 0.1 は画像の 10% がビューポートに入った時点で発火します。ギャラリーページなどで画像の出現に合わせてフェードインアニメーションを適用する場合に有用です。

プレースホルダー戦略と UX の最適化

遅延読み込みで画像が表示されるまでの間、ユーザーに何を見せるかは UX に大きく影響します。空白のままだとレイアウトが不安定に見え、適切なプレースホルダーを表示することで「画像がここに入る」という期待を伝えられます。

主要なプレースホルダー戦略:

プレースホルダーから実画像への遷移にはフェードインアニメーションを適用すると、切り替わりが滑らかになります。opacitytransition を組み合わせ、画像の onload イベントで opacity: 1 に変更する実装が一般的です。ただし、アニメーションの duration は 200〜300ms 程度に抑え、ユーザーを待たせている印象を与えないようにします。

LCP への影響と正しい適用範囲の判断

遅延読み込みの最大の落とし穴は、LCP (Largest Contentful Paint) 対象の画像に適用してしまうことです。LCP はファーストビューで最も大きなコンテンツ要素の表示完了時間を測定する指標であり、多くのページではヒーロー画像やメインビジュアルが LCP 要素になります。

LCP 画像に loading="lazy" を設定すると、ブラウザは画像のダウンロードを遅延させるため、LCP スコアが数百ミリ秒〜数秒悪化します。Google の Lighthouse は「LCP 画像に lazy loading が設定されている」という警告を出しますが、実際のフィールドデータ (CrUX) でも明確な悪影響が確認されています。

正しい適用範囲の判断基準:

LCP 画像には loading="lazy" の代わりに fetchpriority="high" を設定し、さらに <link rel="preload" as="image"> で事前読み込みを指示することで、LCP を最大限に改善できます。この「重要な画像は最優先、それ以外は遅延」という二極化戦略が、Core Web Vitals 最適化の基本です。

フレームワーク別の実装と注意点

モダンなフロントエンドフレームワークは、それぞれ独自の画像最適化コンポーネントを提供しています。これらを活用することで、遅延読み込みの実装を大幅に簡略化できます。

Next.jsnext/image コンポーネントは、デフォルトで遅延読み込みが有効です。priority プロパティを true に設定すると遅延読み込みが無効になり、fetchpriority="high"preload が自動的に適用されます。LCP 画像には必ず priority を設定してください。

React で自前実装する場合、useRefuseEffect で IntersectionObserver を管理するカスタムフックを作成するのが一般的です。コンポーネントのアンマウント時に observer.disconnect() を呼び出してメモリリークを防止することが重要です。

静的サイト (HTML + CSS + JS) では、ネイティブ loading="lazy" を基本とし、プレースホルダーやアニメーションが必要な場合のみ IntersectionObserver を追加する方針が効率的です。ネイティブ属性はブラウザの最適化エンジンと統合されているため、JavaScript 実装よりもパフォーマンスが優れる傾向があります。

いずれのフレームワークでも、画像の widthheight (またはアスペクト比) を事前に確定させることが CLS 防止の鍵です。動的に読み込まれる画像のサイズが不明な場合は、API レスポンスに画像のディメンション情報を含めるか、固定のアスペクト比コンテナで囲む設計にします。

関連記事

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

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

Web 用画像のファイルサイズ最適化戦略 - 品質を保ちながら軽量化する技術

Web パフォーマンスを最大化するための画像ファイルサイズ最適化手法を、フォーマット選択からメタデータ除去まで体系的に解説します。

ブラウザでの画像処理の仕組み - Canvas API、ImageData、Web Workers 活用ガイド

ブラウザ内で画像処理を行う技術的な仕組みを解説。Canvas API によるピクセル操作、ImageData の構造、Web Workers によるオフスレッド処理、OffscreenCanvas の活用方法を紹介します。

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

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

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

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

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

Web ページの画像読み込みを最適化する 3 つの属性を徹底解説。LCP 改善に直結する preload、fetchpriority、decoding の正しい使い方と組み合わせ方を紹介。

関連用語