Webページがブラウザに表示されるまでには、HTMLの解析からピクセルの描画まで、複雑なプロセスが実行されています。このブラウザレンダリングの仕組みを理解することは、パフォーマンス最適化において非常に重要です。
この記事では、DOM構築からコンポジットまでのレンダリングパイプライン全体を詳細に解説し、クリティカルレンダリングパスの最適化やリフロー・リペイントを最小化する実践的なテクニックを紹介します。
この記事で学べること
- ブラウザレンダリングの6つのステップ(DOM、CSSOM、レンダーツリー、レイアウト、ペイント、コンポジット)
- クリティカルレンダリングパスとレンダーブロッキングリソースの回避方法
- リフロー(レイアウト再計算)とリペイント(再描画)の違いと最小化テクニック
- GPU合成を活用したスムーズなアニメーションの実装方法
前提知識と実行環境
この記事を理解するために必要な前提知識と実行環境は以下の通りです。
| 項目 | 内容 |
|---|---|
| 前提知識 | HTML/CSS/JavaScriptの基礎知識 |
| 確認環境 | Google Chrome 131以降、Chrome DevTools |
| 対象読者 | フロントエンドのパフォーマンス最適化に興味があるエンジニア、ブラウザの内部動作を理解したいWeb開発者 |
ブラウザレンダリングパイプラインの全体像
ブラウザがHTMLを受け取ってから画面に描画するまでの流れは、以下の6つのステップで構成されています。
flowchart LR
subgraph パース
A[HTML] --> B[DOM]
C[CSS] --> D[CSSOM]
end
subgraph レンダリング
B --> E[レンダーツリー]
D --> E
E --> F[レイアウト]
F --> G[ペイント]
G --> H[コンポジット]
end
H --> I[画面表示]レンダリングパイプラインの各ステップ
| ステップ | 処理内容 | 出力 |
|---|---|---|
| DOM構築 | HTMLをパースしてDOMツリーを構築 | DOMツリー |
| CSSOM構築 | CSSをパースしてCSSOMツリーを構築 | CSSOMツリー |
| レンダーツリー構築 | DOMとCSSOMを結合して表示対象を決定 | レンダーツリー |
| レイアウト | 各要素の位置とサイズを計算 | ボックスモデル |
| ペイント | 各要素を実際のピクセルに変換 | ペイントレコード |
| コンポジット | レイヤーを合成して最終的な画面を生成 | 表示画面 |
DOM構築とCSSOM構築
DOM(Document Object Model)の構築
ブラウザはHTMLドキュメントを受信すると、バイトをトークンに変換し、トークンをノードに変換してDOMツリーを構築します。
flowchart TB
A[バイト<br/>3C 68 74 6D 6C...] --> B[文字<br/>html head body...]
B --> C[トークン<br/>StartTag: html<br/>StartTag: head...]
C --> D[ノード<br/>HTMLElement<br/>HeadElement...]
D --> E[DOMツリー]以下のHTMLがどのようにDOMツリーに変換されるか見てみましょう。
|
|
このHTMLは以下のようなDOMツリーに変換されます。
flowchart TB
document[document]
html[html]
head[head]
body[body]
meta[meta]
title[title]
link[link]
header[header]
main[main]
h1[h1]
p[p]
document --> html
html --> head
html --> body
head --> meta
head --> title
head --> link
body --> header
body --> main
header --> h1
main --> pCSSOM(CSS Object Model)の構築
CSSOMはCSSをパースして構築されるオブジェクトモデルです。DOMと同様にツリー構造を持ち、CSSの継承とカスケードのルールが適用されます。
|
|
CSSOMツリーでは、親要素のスタイルが子要素に継承されます。例えば、bodyに設定されたfont-familyは、すべての子孫要素に継承されます。
flowchart TB
body["body<br/>font-family: Helvetica Neue<br/>font-size: 16px<br/>line-height: 1.6"]
header["header<br/>background: #333<br/>color: #fff<br/>padding: 20px<br/>+ 継承スタイル"]
main["main<br/>padding: 20px<br/>+ 継承スタイル"]
h1["h1<br/>font-size: 2rem<br/>margin: 0<br/>+ 継承スタイル"]
p["p<br/>color: #666<br/>+ 継承スタイル"]
body --> header
body --> main
header --> h1
main --> pレンダーブロッキングリソースの問題
CSSはレンダーブロッキングリソースです。CSSOMが完成するまで、ブラウザはレンダーツリーを構築できません。これは、CSSOMなしでは要素のスタイルを決定できないためです。
sequenceDiagram
participant Browser as ブラウザ
participant Server as サーバー
Browser->>Server: HTMLリクエスト
Server-->>Browser: HTMLレスポンス
Note over Browser: DOM構築開始
Browser->>Server: CSSリクエスト
Note over Browser: DOM構築完了
Note over Browser: CSSOMを待機中...
Server-->>Browser: CSSレスポンス
Note over Browser: CSSOM構築
Note over Browser: レンダーツリー構築開始レンダーツリーの構築
レンダーツリーは、DOMツリーとCSSOMツリーを組み合わせて構築されます。このツリーには、画面に表示される要素のみが含まれます。
レンダーツリーに含まれない要素
以下の要素はレンダーツリーに含まれません。
<head>、<script>、<meta>などの非表示要素display: noneが適用された要素- CSSで生成されないコンテンツ
|
|
上記の例では、.hiddenはレンダーツリーに含まれませんが、.invisibleはレンダーツリーに含まれます。visibility: hiddenは要素を非表示にしますが、レイアウト上のスペースは確保されるためです。
display: none と visibility: hidden の違い
| プロパティ | レンダーツリー | レイアウト | リフロー発生 |
|---|---|---|---|
display: none |
含まれない | 占有しない | 切り替え時に発生 |
visibility: hidden |
含まれる | 占有する | 切り替え時に発生しない |
opacity: 0 |
含まれる | 占有する | 切り替え時に発生しない |
レイアウト(リフロー)
レイアウトステップでは、レンダーツリー内の各要素の正確な位置とサイズを計算します。この処理は「リフロー」とも呼ばれます。
レイアウト計算の流れ
- ビューポートのサイズを取得
- ルート要素から順にボックスモデルを計算
- 各要素の幅、高さ、マージン、パディング、ボーダーを決定
- 相対的な単位(%、em、rem)を絶対的なピクセル値に変換
|
|
レイアウトを発生させるCSSプロパティ
以下のCSSプロパティを変更すると、レイアウト(リフロー)が発生します。
| カテゴリ | プロパティ |
|---|---|
| サイズ | width, height, padding, margin, border-width |
| 位置 | top, left, right, bottom, position |
| フォント | font-size, font-family, font-weight, line-height |
| テキスト | text-align, vertical-align, white-space |
| その他 | display, float, clear, overflow |
ペイント
ペイントステップでは、レイアウトで計算された情報をもとに、各要素を実際のピクセルに変換します。
ペイントの順序(スタッキングコンテキスト)
ペイントは以下の順序で行われます。
- 背景色(background-color)
- 背景画像(background-image)
- ボーダー(border)
- 子要素
- アウトライン(outline)
ペイントのみを発生させるCSSプロパティ
以下のプロパティはレイアウトを発生させず、ペイント(リペイント)のみを発生させます。
| カテゴリ | プロパティ |
|---|---|
| 色 | color, background-color, border-color |
| 影 | box-shadow, text-shadow |
| その他 | visibility, outline, background-image |
コンポジット(合成)
コンポジットは、複数のレイヤーを合成して最終的な画面を生成するステップです。このステップはGPUで処理されるため、非常に高速です。
レイヤーが作成される条件
以下の条件を満たす要素は、独自の合成レイヤーを持ちます。
|
|
コンポジットのみを発生させるCSSプロパティ
以下のプロパティは、レイアウトやペイントを発生させず、コンポジットのみで処理されます。
| プロパティ | 説明 |
|---|---|
transform |
要素の変形(移動、回転、拡大縮小) |
opacity |
透明度の変更 |
これらのプロパティは、GPUで処理されるため、60fpsのスムーズなアニメーションを実現できます。
クリティカルレンダリングパスの最適化
クリティカルレンダリングパスとは、ブラウザが最初のピクセルを描画するまでに必要な一連のステップです。このパスを最適化することで、First Contentful Paint(FCP)を改善できます。
1. レンダーブロッキングCSSの最適化
CSSはレンダーブロッキングリソースですが、適切な対策で影響を軽減できます。
|
|
2. メディアクエリによるCSSの分割
デバイスやビューポートに応じてCSSを分割することで、不要なCSSの読み込みを防ぎます。
|
|
3. JavaScriptのパーサーブロッキング回避
JavaScriptはDOMのパースをブロックします。asyncやdefer属性を使用して、パーサーブロッキングを回避しましょう。
|
|
async と defer の違い
sequenceDiagram
participant HTML as HTMLパース
participant JS as JSダウンロード
participant Exec as JS実行
Note over HTML,Exec: 通常の script
HTML->>HTML: パース中断
JS->>JS: ダウンロード
Exec->>Exec: 実行
HTML->>HTML: パース再開
Note over HTML,Exec: async 属性
HTML->>HTML: パース継続
JS->>JS: 並行ダウンロード
HTML->>HTML: パース中断
Exec->>Exec: ダウンロード完了後即実行
HTML->>HTML: パース再開
Note over HTML,Exec: defer 属性
HTML->>HTML: パース継続
JS->>JS: 並行ダウンロード
HTML->>HTML: パース完了
Exec->>Exec: パース完了後に実行| 属性 | ダウンロード | 実行タイミング | 実行順序 | 用途 |
|---|---|---|---|---|
| なし | ブロッキング | 即座 | 記述順 | DOM操作が必要なスクリプト |
async |
並行 | ダウンロード完了後 | 不定 | 独立したスクリプト(アナリティクスなど) |
defer |
並行 | DOMContentLoaded前 | 記述順 | DOM操作が必要なスクリプト |
リフローとリペイントの最小化
リフロー(レイアウト再計算)は、ブラウザレンダリングにおいて最もコストの高い処理です。パフォーマンス最適化では、リフローの発生回数を最小限に抑えることが重要です。
リフローを引き起こす操作
以下の操作はリフローを引き起こします。
|
|
強制同期レイアウト(Layout Thrashing)を避ける
読み取りと書き込みを交互に行うと、ブラウザは毎回レイアウトを再計算する必要があります。これを「強制同期レイアウト」または「Layout Thrashing」と呼びます。
|
|
requestAnimationFrame を使用した最適化
requestAnimationFrameを使用することで、ブラウザの描画タイミングに合わせてDOM操作を行えます。
|
|
DocumentFragment を使用したバッチDOM操作
複数のDOM要素を追加する場合は、DocumentFragmentを使用してバッチ処理を行います。
|
|
GPU合成を活用したアニメーション最適化
transformとopacityプロパティを使用したアニメーションは、GPUで処理されるため、60fpsのスムーズなアニメーションを実現できます。
transform を使用した移動アニメーション
|
|
will-change による最適化ヒント
will-changeプロパティを使用して、ブラウザに最適化のヒントを与えることができます。
|
|
|
|
注意:will-changeはメモリを消費するため、必要な要素にのみ使用し、アニメーション完了後は解除することを推奨します。
パフォーマンス比較
以下の表は、異なるアニメーション手法のパフォーマンス比較です。
| アニメーション手法 | リフロー | リペイント | GPU合成 | 推奨度 |
|---|---|---|---|---|
left/top |
発生 | 発生 | 発生 | 非推奨 |
margin |
発生 | 発生 | 発生 | 非推奨 |
transform |
なし | なし | 発生 | 推奨 |
opacity |
なし | なし | 発生 | 推奨 |
Chrome DevTools を使用したパフォーマンス計測
Chrome DevToolsのPerformanceパネルを使用して、レンダリングパフォーマンスを計測できます。
Performance パネルの使い方
- DevToolsを開く(F12 または Ctrl+Shift+I)
- Performanceタブを選択
- 録画ボタンをクリックしてプロファイリングを開始
- 計測したい操作を行う
- 停止ボタンをクリック
確認すべき指標
| 指標 | 説明 | 目標値 |
|---|---|---|
| Scripting | JavaScript実行時間 | フレームあたり16ms以下 |
| Rendering | レイアウト計算時間 | 最小化 |
| Painting | ペイント時間 | 最小化 |
| FPS | フレームレート | 60fps |
Rendering タブの活用
DevToolsのRenderingタブでは、ペイントやレイヤーを視覚化できます。
- DevToolsで「Rendering」パネルを開く(Ctrl+Shift+P → 「Show Rendering」)
- 「Paint flashing」を有効化してペイント領域を確認
- 「Layout Shift Regions」でレイアウトシフトを可視化
- 「Layer borders」でコンポジットレイヤーを確認
実践的な最適化チェックリスト
以下のチェックリストを参考に、Webページのレンダリングパフォーマンスを最適化してください。
クリティカルレンダリングパス
- クリティカルCSSをインライン化している
- 非クリティカルCSSは非同期で読み込んでいる
- JavaScriptにはasyncまたはdefer属性を付与している
-
<link rel="preload">でクリティカルリソースを事前読み込みしている
リフローの最小化
- レイアウト情報の読み取りと書き込みを分離している
-
requestAnimationFrameを使用してDOM更新をバッチ処理している -
DocumentFragmentを使用して複数要素の追加をバッチ処理している - レイアウトを引き起こすプロパティの変更を最小化している
アニメーション最適化
- アニメーションには
transformとopacityを使用している -
left/top/marginではなくtransformで要素を移動している - 必要に応じて
will-changeを使用している - アニメーション完了後は
will-changeを解除している
画像とリソース
- 適切なサイズの画像を使用している
- 遅延読み込み(
loading="lazy")を活用している - 画像のアスペクト比を指定してCLSを防止している
まとめ
ブラウザレンダリングの仕組みを理解することで、パフォーマンス最適化のための効果的な対策を講じることができます。
主要なポイントを振り返りましょう。
- レンダリングパイプライン:DOM構築 → CSSOM構築 → レンダーツリー → レイアウト → ペイント → コンポジットの6ステップで画面が描画される
- クリティカルレンダリングパス:クリティカルCSSのインライン化、非同期CSS/JSの読み込みで最適化
- リフローの最小化:読み取りと書き込みの分離、
requestAnimationFrameの活用、DocumentFragmentの使用 - GPU合成の活用:アニメーションには
transformとopacityを使用してスムーズな60fpsを実現
これらの知識を活かして、高速で快適なWebページを構築してください。