【実務・中級編】 ネイティブLazy Loadingとレンダリング – Webブラウザの仕組み実践ガイド

やあ。今日も今日とて、プロダクトの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”` が指定された `` や `