【実務・中級編】 レンダリングパイプラインの全工程 – Webブラウザの仕組み実践ガイド

やあ。現場でコードを書いていて、「なぜかカクつく」「レンダリングが重い」という壁にぶつかったことはないかい?

ブラウザのレンダリングパイプラインを理解することは、フロントエンドエンジニアにとっての「解剖学」だ。ここを避けて通る者は、いつまで経っても「なぜか直らないCSS」や「謎の重さ」に振り回されることになる。

今日は、ブラウザが裏側で何をやっているのか、その泥臭い現場を覗いてみよう。

—

レンダリングパイプラインの深層:ブラウザは「何」をしているのか

ブラウザがHTMLを受け取ってから画面にピクセルを描画するまでには、大きく分けて以下の工程がある。

1. DOM/CSSOM構築: HTML/CSSの解析。まずは素材集め。
2. Recalculate Style: DOMとCSSOMをマージして、「どのノードにどのスタイルが当たるか」を計算する。
3. Layout (Reflow): 各ノードの幾何学的な位置とサイズを計算する。これが一番の重労働だ。
4. Paint: 色、影、境界線などをビットマップに描画する。
5. Composite: 描画されたレイヤーを重ね合わせ、GPUに送って画面に出力する。

多くのエンジニアが陥る罠は、「JavaScriptでDOMをいじると、このパイプラインがどこから再走するのか」を意識していないことにある。

1. Recalculate Style(スタイルの再計算)

JSでクラスを書き換えると、ブラウザは「影響を受けるノードはどこだ?」と木構造を辿り始める。CSSセレクタが複雑であればあるほど、この探索コストは跳ね上がる。

2. Layout(レイアウト)

要素の幅や高さを変えると、その子要素や親要素、隣接する要素の位置まで再計算が必要になる。これが「リフロー」だ。特に`flex`や`grid`を多用していると、計算量は指数関数的に増える可能性がある。

3. Paint(ペイント)

背景色や文字色を変えるだけならレイアウトは走らないが、ペイントは走る。これ自体はさほど重くないが、範囲が広ければ相応のコストがかかる。

4. Composite(コンポジット)

ここが現代のレンダリングの要だ。「CSSの `transform` や `opacity` だけを変える」と、レイアウトもペイントもスキップして、GPUで画像を動かすだけの「コンポジット」だけで完結する。これが60fps(あるいは120fps)を維持する魔法の領域だ。

—

ボトルネックを特定する:Chrome DevToolsの「真実」

勘でコードを書くのはやめよう。Chrome DevToolsの 「Performance」タブ を開くんだ。

  • 紫色(Recalculate Style) や オレンジ色(Layout) が長く積み上がっている場所はないか?
  • その直前に、自分が実行した `element.style.width = …` のようなJSが動いていないか?

「強制同期レイアウト(Forced Synchronous Layout)」というアンチパターンがある。JSでスタイルを書き換えた直後に `offsetHeight` 等を取得すると、ブラウザは溜まっていたレイアウト計算を「今すぐやれ!」と強制される。これがカクつきの元凶だ。

—

実践:パフォーマンスを最適化するコード設計

例えば、リストの要素を頻繁に動かしたい場合、どう書くのが正解か。以下のコードを見比べてほしい。

// 【アンチパターン】
// 読み取りと書き込みが交互に発生し、毎回レイアウト計算を強制している
const items = document.querySelectorAll(‘.list-item’);
items.forEach(item => {
const height = item.offsetHeight; // 読み取り(レイアウト発生!)
item.style.height = (height + 10) + ‘px’; // 書き込み(レイアウト発生!)
});

// 【ベストプラクティス】
// 読み取りを一括で済ませてから、書き込みを一括で行う(Batching)
const items = document.querySelectorAll(‘.list-item’);

// 1. 読み取りフェーズ
const heights = Array.from(items).map(item => item.offsetHeight);

// 2. 書き込みフェーズ
items.forEach((item, index) => {
item.style.height = (heights[index] + 10) + ‘px’;
});

なぜこれが速いのか?

ブラウザは本来、タスクの最後にまとめてレイアウト計算を行おうとする(これを「レイアウトの遅延評価」と呼ぶ)。上記のアンチパターンでは、JSが強制的にその列を割り込ませているんだ。読み取りと書き込みを分けるだけで、ブラウザが最適化する余地を確保できる。

—

現場のチーフアーキテクトからのアドバイス

最後に、一つだけ覚えて帰ってくれ。

「レイアウトをトリガーするプロパティを避ける」

`top`, `left`, `width`, `height` は避ける。代わりに `transform: translate(…)` や `scale()` を使う。これらはレイアウトのパイプラインを飛ばして、GPUの得意な「コンポジット」だけで処理してくれる。

Webブラウザは非常に賢い。だが、我々エンジニアの書き方次第で、その賢さを殺すこともできれば、最大限に引き出すこともできる。

「なんとなく動く」から「仕組みを理解して制御する」へ。一段上のエンジニアを目指すなら、まずは今書いているその `style` の操作が、ブラウザのパイプラインのどこを叩いているか、想像することから始めてみてほしい。

現場からは以上だ。また何かあればいつでも聞いてくれ。

コメント

タイトルとURLをコピーしました