モバイルでオフスクリーン画像を遅延読み込みする

モバイルでオフスクリーン画像を遅延読み込みする

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-01-23

モバイル画像の遅延読み込み:標準的な方法

モバイルのパフォーマンスは、ネットワークレイテンシー(RTT)とメインスレッドCPUの可用性によって制限されることがよくあります。モバイルでオフスクリーン画像を遅延読み込みすることで、クリティカルレンダリングパスにおける帯域幅の競合を防ぎ、画像デコードのコストをセッション全体に分散させ、これら両方の問題に対処します。

このドキュメントでは、モバイルで画像を効果的に遅延読み込みする方法、使用すべきタイミング、そしてモバイルビューポート特有の技術的制約について解説します。

1. モバイルでオフスクリーン画像を遅延読み込み:ネイティブ lazy loading

ブラウザがページを読み込む際、限られた数の並列接続を開きます(多くの要因に依存しますが、ドメインあたり6本が一般的な平均です)。これらの接続がオフスクリーン画像のダウンロード(例:フッターのロゴやカルーセルのスライド)に使用されると、重要なリソースのダウンロード(通常はLCP画像、重要なスクリプトやフォントがスロットと帯域幅を奪い合います)が競合します。ネットワーク競合として知られるこの現象は、Core Web Vitalsを直接的に悪化させます。

ネイティブのloading属性を使用してオフスクリーン画像を遅延読み込みすることで、重要なリソースを優先し、クリティカルレンダリングパスを最適化できます。ブラウザはすぐに表示されるものだけを取得し、First Contentful Paint(FCP)とLargest Contentful Paint(LCP)に直接影響するアセットのために帯域幅を確保します。ネイティブ lazy loading はこの優先順位付けロジックをブラウザのはるかに高速な内部メカニズムに委ね、古くて遅い JavaScript ライブラリの必要性を排除します。

実装方法

初期ビューポート(「ファーストビュー」)より下にあるすべての画像にloading="lazy"属性を追加します。

<!-- Standard Deferred Image -->
<img src="product-detail.jpg"
     loading="lazy"
     alt="Side view of the chassis"
     width="800"
     height="600"
     decoding="async">

モバイルでの lazy loading の仕組み:ブラウザのヒューリスティック

ネイティブ lazy loading は JavaScript ソリューションよりも優れています。ブラウザが実効接続タイプ(ECT)に基づいて読み込みしきい値(画像のダウンロードがトリガーされるタイミング)を調整するためです。

  • 4G/WiFi の場合:Blink エンジン(Chrome/Edge)は保守的なしきい値(例:1250px)を使用します。低レイテンシーを前提として、ユーザーがビューポートに比較的近づいた時点でのみ画像を取得します。
  • 3G/Slow-2G の場合:しきい値が拡大されます(例:2500px)。ブラウザは高いラウンドトリップタイムを補うために、スクロール位置に対してはるかに早くリクエストを開始し、ユーザーがスクロールして表示する前に画像の準備を完了させます。

重要な例外:LCP 候補

開発者が Largest Contentful Paint(LCP)要素(通常はヒーロー画像)に loading="lazy" を適用すると、一般的なパフォーマンス低下が発生します。これにより、レイアウトが完了するまで取得が遅延されます。

正しい LCP 戦略:LCP 画像は eager-loaded で優先的に読み込む必要があります。

<!-- Hero Image: Eager and Prioritized -->
<img src="hero.jpg"
     alt="Summer Collection"
     width="1200"
     height="800"
     loading="eager"
     fetchpriority="high"

2. モバイル特有の複雑さ:ビューポートとタッチ

モバイルビューポートには特有のレンダリング課題があり、ネイティブ実装はスクリプトベースのソリューションよりも堅牢に処理します。

  • ビューポート:ブラウザウィンドウの可視矩形領域。モバイルではこれは動的であり、デバイスの向き(縦向き vs. 横向き)やブラウザクロームの状態(URLバーの格納)に基づいてサイズが変化します。
  • ファーストビュー:ビューポートの正確な下端。表示コンテンツとオフスクリーンコンテンツを分離するしきい値です。
  • ファーストビューより上:スクロールなしでページ読み込み時にすぐに表示されるコンテンツ。ここにある画像は重要であることが多く、ほぼ lazy-loaded にすべきではありません。
  • ファーストビューより下:ファーストビューより垂直方向に下に位置するコンテンツ。このコンテンツは非クリティカルであり、ユーザーが近くにスクロールするまで遅延読み込みする必要があります。

動的ビューポート

モバイルブラウザでは、ビューポートの高さ(vh)は可変です。ユーザーがタッチスクロールを開始すると、URLバーやナビゲーションコントロールが格納されることが多く、表示領域のサイズが変化します。

JavaScript ベースの画像遅延読み込みライブラリは通常、ページ読み込みの開始時に一度だけビューポートの高さ(window.innerHeight)を計算します。  モバイルブラウザがスクロール中にURLバーを非表示にして表示領域を動的にリサイズしても、JavaScript の方式は古い小さな高さの値を使い続けます。これにより、物理的に拡大されたビューポート領域に入った画像でも読み込まれないままとなり、訪問者のUXの低下を引き起こしました。

ネイティブ処理ではこの問題が解決されます。ブラウザの内部レイアウトエンジンがビジュアルビューポートを自動的に追跡するため、ビューポートサイズの変更に関係なくトリガーが確実に発火します。

3. モバイルでの画像デコードとCPUスロットリング

モバイルデバイスはCPUが限られており、モバイルでの画像デコードは比較的遅くコストがかかる場合があります。JPEGをビットマップに変換するには多くのCPUサイクルが必要です。モバイルプロセッサでは、大きな画像を連続してデコードすると、1枚あたり50ms〜100msメインスレッドをブロックし、入力レイテンシーを引き起こす可能性があります。

解決策:content-visibility

この問題を解決するために、CSSプロパティと値content-visibility: autoを使用できます。このプロパティは「遅延レンダリング」の標準として機能します。オフスクリーン要素のレイアウトとペイントフェーズを完全にスキップするようブラウザに指示します。要素はDOMに存在しますが、ビューポートに近づくまでレンダーツリーには存在しません。

この最適化は要素のサブツリーのレンダリングをスキップすることで機能するため、<img>タグ(サブツリーを持たない)に直接適用することはできません。これらの画像をホストするプロダクトコンテナや画像カードに content-visibility を適用し、そのコンテンツに対して使用する必要があります。

@media (max-width: 768px) {
    .image-card, .product-card {
        /* Skip rendering of the container and its children */
        content-visibility: auto;
        
        /* Essential: Prevents container from collapsing to 0px height */
        contain-intrinsic-size: auto 300px;
    }
}

これにより、画像がダウンロードされても、ユーザーが実際にスクロールするまでブラウザはレイアウト/ペイントのコストを支払わないことが保証されます。

4. レガシー手法:避けるべき理由

ネイティブサポート以前、開発者はモバイルでの画像遅延読み込みに JavaScript に頼っていました。これらの方式は今でも広く使用されていますが、技術的負債として考えるべきです。

「スクロールハンドラー」時代(2010年〜2016年)

初期の実装では、スクロールイベントにイベントリスナーをアタッチしていました。

// OBSOLETE: Do not use
window.addEventListener('scroll', () => {
    images.forEach(img => {
        if (img.getBoundingClientRect().top < window.innerHeight) {
            img.src = img.dataset.src;
        }
    });
});

メインスレッドのブロック:スクロールイベントは1秒間に数十回発火します。アクティブなスクロール中にロジックを実行しレイアウトを計算(getBoundingClientRect)すると、フレーム落ち(ジャンク)が発生しました。

レイアウトスラッシング:ジオメトリプロパティをクエリすると、ブラウザにレイアウトスタイルの同期的な再計算を強制します。これはモバイルCPUでは計算コストの高い操作です。 

IntersectionObserver 時代(2016年〜2019年)

IntersectionObserver APIは、要素の可視性の変化を非同期で監視することでパフォーマンスを向上させました。 

// DEPRECATED: Use native loading where possible
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            observer.unobserve(img);
        }
    });
});

スクリプト依存:JavaScript の実行が必要です。メインスレッドがフレームワーク(React/Vue)のハイドレーションで忙しい場合、画像がビューポート内にあっても読み込まれないままになります。

ネットワーク認識の欠如:ネイティブ loading とは異なり、IntersectionObserver は固定マージン(例:rootMargin: '200px')を使用します。低速ネットワーク上で自動的にバッファを拡大することがないため、接続が不安定なユーザーに「白いフラッシュ」が発生します。


CrUX data is 28 days late.

Google provides data 28 days late. CoreDash provides data in real-time. Debug faster.

Get Real-Time Data >>

  • Real-Time Insights
  • Faster Debugging
  • Instant Feedback
モバイルでオフスクリーン画像を遅延読み込みする Core Web Vitals モバイルでオフスクリーン画像を遅延読み込みする