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

こんにちは、フロントエンドの深淵を覗くのが趣味のエンジニアの皆さん。

ブラウザのレンダリングパイプラインを日々チューニングしていると、HTMLのパーサーがトークナイザーを通じて文字列をバリバリとバイト列からDOMノードへ変換していくあの瞬間が、なぜか愛おしくなってくるものです。特に、現代のWebアプリケーションにおいて「どうやってブラウザのメインスレッドを解放し、初期描画のクリティカルパスを最適化するか」は、シニア以上のフロントエンドエンジニアにとって永遠の課題であり、腕の見せ所でもあります。

今回は、その中でも特に見落とされがち、かつブラウザのメモリ効率とレンダリング負荷に直接的なパンチを叩き込む「ネイティブ Lazy Loading(`loading=”lazy”`)」の内部挙動について、ブラウザエンジンの裏側を覗きながらガッツリ深掘りしていきましょう。

—

ネイティブ Lazy Loading とレンダリングパイプラインの蜜月関係

かつて、私たちはIntersection Observer APIを駆使し、スクロールイベントのデバウンス処理に涙を流しながら、自前で画像の遅延読み込み機構を実装していました。メインスレッドでJavaScriptがブルブルと震えながら要素の位置を計算し、ビューポートに入った瞬間に`src`を書き換える……。あれは本当に泥臭い戦いでした。

しかし、HTML5と各ブラウザエンジンの進化によって、今や私たちは `loading=”lazy”` という呪文を記述するだけで、この重労働をブラウザのC++コア(BlinkやWebKit)の最適化されたレイヤーに丸投げできるようになりました。

ここで一度、HTMLパーサーがDOMツリーを構築し、それがCSSOMと結合してレンダーツリー(Render Tree)を形成するまでのプロセスを思い出してください。

[ HTML Stream ]
↓
[ Tokenizer ]
↓
[ DOM Tree 構築 ] ← (loading=”lazy” の属性評価)
↓
[ CSSOM 結合 ] → [ Render Tree 構築 ] → [ Layout / Paint ]

通常、ブラウザは `` や `