Web●●●○○

ブラウザレンダリング

ぶらうざれんだりんぐ

HTML・CSSが画面のピクセルになるまでの過程。この理解が表示速度の最適化を支える。

概要

ブラウザレンダリングは、サーバから届いた HTMLCSS という文字列が、画面上のピクセルとして表示されるまでの一連の処理過程です。ブラウザは受け取った文書を解析して木構造を組み立て、スタイルを計算し、各要素の位置と大きさを決め、色を塗り、最後に層を重ね合わせる — この流れは「レンダリングパイプライン」と呼ばれ、ページを開くたび、そして画面が変化するたびに動いています。

普段は意識しなくても動く仕組みですが、「ページの表示が遅い」「スクロールがカクつく」「操作してから反応するまでが重い」といった問題の原因は、ほぼ必ずこのパイプラインのどこかにあります。どの段階で何が起き、何がそれをブロックし、どの操作がどの段階からやり直しになるのか — この地図を持っているかどうかが、フロントエンドの速度改善の精度を分けます。

なぜ生まれたか

レンダリングの仕組み自体はブラウザの誕生とともにありましたが、開発者がそれを「知るべき知識」として学ぶようになったのは、Webがアプリケーション化してからです。静的な文書を表示するだけの時代は、描画は一度きりで終わり、速度はほぼ回線で決まりました。ところが JavaScriptDOM を頻繁に書き換え、アニメーションやスクロール連動が当たり前になると、描画は「毎秒60回締め切りが来る連続処理」になります。1フレームに使える時間は約16ミリ秒。この予算を超えた瞬間にカクつきとして体感される世界では、パイプラインのどの段階が高くつくかを知らずに書いたコードが、そのまま遅さになって現れます。

さらに、表示速度が検索順位や離脱率に直結することが数字で示されるようになり、GoogleはCore Web Vitalsという指標群でそれを検索評価に組み込みました。レンダリングの理解は、ブラウザ実装者の内輪の知識から、Web開発者の共通教養へと変わったのです。

詳細

パイプラインを順に追う

ブラウザはまず HTML を上から解析(パース)して DOM ツリーを構築します。並行して CSS を解析し、セレクタと宣言の集合である CSSOM(CSS Object Model)を作ります。この2つを突き合わせ、実際に表示される要素だけを集めたレンダーツリーができると、次はレイアウト — 各要素の位置と大きさをビューポート基準で計算する工程です。続くペイントで文字・色・影などを描画命令に変換し、最後のコンポジット(合成)で、複数のレイヤを GPU で重ね合わせて1枚の画面に仕上げます。

HTMLCSSパースパースDOMツリーCSSOMレンダーツリーレイアウト位置と大きさを計算ペイント色や文字を描画コンポジットレイヤをGPUで合成左の工程からやり直すほど高コスト。変更がどこから再実行を招くかがチューニングの鍵
レンダリングパイプライン — HTMLとCSSがピクセルになるまで

なお、実際のレイアウト計算の直前には「(矢印の合流点にあたる)どの要素にどのスタイルが当たるか」を確定させるスタイル計算があり、DOMツリーとCSSOMの合流はこの工程を指します。display: none の要素はレンダーツリーに含まれず、以降の工程から完全に外れます。

レンダリングブロッキング — 最初の表示を止めるもの

この最初の1回のパイプライン、すなわちクリティカルレンダリングパスを最短にすることが初期表示の最適化です。ここで問題になるのがブロッキングです。CSS は「CSSOM が完成するまで描画できない」ためレンダリングブロッキングであり、<head> 内の同期的な <script> は「DOM を書き換えるかもしれない」ためパース自体を止めます。だからこそ、CSSは可能な限り小さく早く届け、JavaScript には deferasync を付けてパースを止めない、ファーストビューに必要な最小限のCSSだけをインライン化する、といった定石が生まれました。SSRSSG が「最初から描画可能なHTMLを返す」ことで初期表示に強いのも、このパスを短縮しているからです。

リフローとリペイント — 変更のコストは段階で決まる

描画後に画面を変更するとき、パイプラインの「どこからやり直すか」でコストが桁違いに変わります。要素の幅・高さ・位置など形に関わる変更はレイアウトからやり直しになり、これをリフロー(再レイアウト)と呼びます。ある要素のサイズ変更が親や兄弟の配置に波及するため、最も高くつく操作です。一方、色や影など形を変えない変更はペイントからのやり直し(リペイント)で済み、リフローよりは軽い。そして transformopacity の変更は多くの場合コンポジットだけで処理でき、最も安価です。アニメーションを left を動かして作るとカクつくのに transform: translateX なら滑らかなのは、前者が毎フレームリフローを起こし、後者は合成だけで済むからです。

もうひとつの古典的な罠が「強制同期レイアウト」です。JavaScript でスタイルを書き換えた直後に offsetHeight などレイアウト結果を読むと、ブラウザは正確な値を返すためにその場でリフローを強制されます。読み書きをループ内で交互に行うと「レイアウトスラッシング」と呼ばれる連続リフローになり、一気に性能が崩れます。

GPU合成レイヤと Core Web Vitals

コンポジット工程では、ブラウザは一部の要素を独立した合成レイヤに昇格させます。レイヤは GPU 上のテクスチャとして保持されるため、位置や透明度の変更は再描画なしで合成し直すだけで反映できます。will-change などでレイヤ化を促せますが、レイヤはメモリを食うため乱用は逆効果 — 「動かすものだけ昇格させる」のが原則です。

こうした知識を実務の目標値に落とし込んだのが Core Web Vitals です。LCP(Largest Contentful Paint)は主要コンテンツの表示までの速さでクリティカルレンダリングパスの成績を、INP(Interaction to Next Paint)は操作への応答性でメインスレッドの詰まりを、CLS(Cumulative Layout Shift)は表示中のレイアウトのずれ、つまり意図しないリフローの積み重ねを測ります。改善の手筋 — 画像への寸法指定でCLSを防ぐ、CDNキャッシュ で取得を速めてLCPを縮める、長い JavaScript タスクを分割してINPを守る — は、どれもこのパイプラインの地図の上に載っています。計測には Chrome DevTools の Performance パネルと Lighthouse が定番で、推測ではなく記録されたフレームを見て直すのが鉄則です。