やあ。今日も今日とて、プロダクトのCore Web Vitalsのスコアボードとにらめっこってところか。
LCP(Largest Contentful Paint)やCLSの数値改善に頭を悩ませているなら、君はもう一段上のフロントエンドの景色を見るフェーズに来ている証拠だ。
今回は、実務で毎日お世話になっているはずの `loading=”lazy”`(ネイティブLazy Loading) について、ブラウザの心臓部であるレンダリングエンジンの挙動と絡めて、徹底的に解剖していこう。
ネット上の適当な記事だと「とりあえず画像に `loading=”lazy”` を付ければ高速化します!」なんてフワッとした説明で終わっていることが多いが、シニアを名乗るなら、「ブラウザが裏側でどういう計算をし、DOMツリーやCSSOMツリー、そしてクリティカルレンダリングパスにどう影響しているのか」を正確に理解しておく必要がある。
知らぬ間に「逆にレンダリングを遅らせていた」なんて悲劇を生まないための実践知を、ここで叩き込んでいってくれ。
—
1. 前提:HTMLパーサとレンダリングエンジンの裏側の動き
まず、ブラウザがHTMLを読み込んで画面にピクセルを描画するまでの基本的な流れ(クリティカルレンダリングパス)を簡単におさらいしておこう。
1. HTMLパース: ネットワーク経由でバイトデータを受け取り、文字コードに変換し、トークン化して、最終的にDOM(Document Object Model)ツリーを構築する。
2. CSSOM構築: ``タグなどで外部CSSを見つけると、それを並行してダウンロード・解析し、CSSOM(CSS Object Model)ツリーを作る。
3. レンダーツリー構築: DOMとCSSOMを合体させて、画面に表示すべき要素だけを抽出し、レンダーツリーを作る。
4. レイアウト(リフロー): 各要素が画面上のどこにどれくらいのサイズで配置されるかを幾何学的に計算する。
5. ペイント(ラスタライズ): ピクセル単位に分解して画面に描き出す。
ここで重要なのは、ブラウザのメインスレッドは常に大忙しだということだ。HTMLパーサが上から下へ進んでいく最中に、画像やスクリプトのロードが挟まると、レンダリングが一時停止したりリソースを奪われたりする。
そこで登場するのが、HTML5の標準機能であり、いまやブラウザネイティブでサポートされている `loading=”lazy”` だ。
—
2. `loading=”lazy”` の真のメカニズム:ブラウザはどう判断しているのか?
JavaScriptのIntersection Observerを使った遅延読み込みライブラリ全盛期だった時代を覚えているかい? あの頃はJSのバンドルサイズが肥大化したり、メインスレッドのアイドル時間を奪ったりと、なかなかの苦労があった。
いまやモダンブラウザ(Chromium, Safari, Firefox)は、これをブラウザのC++層(レンダリングエンジン側)でネイティブに処理している。
ビューポートからの「距離」の概念
ブラウザは、`loading=”lazy”` が指定された `` や `
具体的にいつリクエストが走るのか?
それは、ユーザーがスクロールして、その要素が「現在のビューポート(画面に見えている領域)から一定の距離(Distance Threshold)以内」に近づいた瞬間だ。
この「一定の距離」は、ブラウザの実装やネットワークの接続速度(下り回線の速さやデータセーブモードの有無)によって動的に調整される。
- 高速回線なら、ユーザーがスクロールして視界に入る少し手前(数画面分あるいは数百ピクセル手前)で先読みを開始する。
- 低速回線(2G/3G)やデータセーブモードが有効な場合は、ビューポートのギリギリ直前までリクエストを我慢する。
レンダリングパスへの具体的な影響
ここで「おや?」と思った鋭い後輩なら合格点だ。
画像自体の読み込みが遅延するということは、「その画像分のレイアウト領域(幅と高さ)」が確定していないと、後から画像が読み込まれた瞬間にガクッとレイアウトがズレる(=CLSの悪化)という問題が起きる。
だからこそ、ネイティブLazy Loadingを使うときは、必ず `width` と `height` 属性(あるいはCSSのアスペクト比)を明示してプレースホルダーの領域をあらかじめ確保しておくのが鉄則なのだ。これがないと、ブラウザはレイアウト計算を二度やり直すハメになり、パフォーマンスが台無しになる。
—
3. 実務で使える!ベストプラクティスとコード例
理論が分かったところで、現場ですぐに使える具体的な実装パターンを見ていこう。
今回は、リッチなECサイトやメディアサイトでよくある「ファーストビュー(LCP候補)と、それ以降のスクロール先」を綺麗に書き分ける例だ。
プロダクトのメインビジュアル

ここにスクロールを促すコンテンツが続きます…
おすすめの商品一覧
コードの解説・シニアからのワンポイントアドバイス
1. LCP画像に `loading=”lazy”` を絶対につけないこと
一番やりがちなミスが、「サイト内の画像全部に一律で `loading=”lazy”` をループ出力する」という実装だ。ファーストビュー(LCP要素)にこれをやってしまうと、ブラウザがCSSやHTMLをパースし終えて「あ、この画像はLazyなんだな」と気づいてから初めてネットワークリクエストを飛ばすため、LCPの計測タイミングが大幅に遅れ、Googleからの評価(SEO)がガタ落ちする。LCP候補にはむしろ `fetchpriority=”high”` を与えるのが正解だ。
2. `width` と `height` は「属性」で書く
CSSでサイズを指定するだけではなく、HTMLの属性として `width` と `height` を明記してほしい。ブラウザのHTMLパーサは、CSSOMの構築を待たずにHTMLを読んだ瞬間にアスペクト比(Intrinsic Aspect Ratio)を計算し、レイアウトスペースを確保できるため、CLSを完璧に防げる。
3. `decoding=”async”` との組み合わせ
画像データのデコード(CPUに負荷がかかるピクセル展開処理)をメインスレッドから非同期に逃がす `decoding=”async”` を併用すると、スクロール時のカクつき(フレームドロップ)を防ぎ、スクロールパフォーマンスが劇的に滑らかになる。セットで覚えておこう。
—
4. まとめ
ブラウザのレンダリングメカニズムを理解していれば、`loading=”lazy”` は単なる便利機能ではなく、「ブラウザのメインスレッドの負荷を軽減し、ネットワーク帯域を本当に必要なリソースへ優先配分するための強力なアーキテクチャ上の武器」であることが見えてくるはずだ。
「とりあえず動く」から一歩踏み込んで、ブラウザが裏側でどう動いているかを想像しながらコードを書く。それこそが、周囲から信頼されるフロントエンドエンジニアへの近道だ。
さて、理論はここまでだ。君のプロジェクトのコードベースを開いて、LCP画像にうっかり `loading=”lazy”` がついていないか、今すぐ確認しに行こうか!

コメント