メインコンテンツへスキップ
Code & Craft
Web制作 約8分で読めます

ブラウザのレンダリングプロセス完全解説|HTMLからピクセルまでの舞台裏【図解】

ブラウザがHTML/CSS/JSをどう画面に表示するかを図解付きで完全解説。レンダリングパイプライン、Critical Rendering Path、リフロー・リペイントの最適化を学べる。

Web パフォーマンス最適化の第一歩は、ブラウザがどのように HTML を画面に描画しているかを理解することです。レンダリングパイプラインを知れば、なぜ特定の CSS プロパティが遅いのか、なぜ transform がアニメーションに適しているのかが論理的にわかるようになります。

レンダリングパイプラインの全体像

ブラウザは以下の 5 段階で HTML を画面に表示します。

HTML + CSS + JS

1. Parse(パース)

2. Style(スタイル計算)

3. Layout(レイアウト)

4. Paint(ペイント)

5. Composite(コンポジット)

  画面表示

各ステップを詳しく見ていきましょう。

ステップ1: Parse(パース)

HTML パーサーは、HTML を上から順に読み取り、DOM ツリーを構築します。

<html>
  <body>
    <h1>Hello</h1>
    <p>World</p>
  </body>
</html>
Document
└ html
  └ body
    ├ h1
    │ └ "Hello"
    └ p
      └ "World"

同時に CSS パーサーが CSS を解析し、CSSOM ツリーを構築します。

JavaScript によるブロッキング

重要なのは、JavaScript は HTML パースをブロックすることです。

<p>ここまでは描画される</p>
<script src="heavy.js"></script>
<p>この段落は script 完了後まで描画されない</p>

対策: async または defer を使う。

<!-- パースと並行してダウンロード、ダウンロード完了時に実行 -->
<script src="app.js" async></script>

<!-- パースと並行してダウンロード、パース完了後に実行 -->
<script src="app.js" defer></script>

ステップ2: Style(スタイル計算)

DOM ツリーと CSSOM ツリーを結合し、各要素に適用されるスタイルを決定します。これは Render Tree と呼ばれます。

DOM + CSSOM → Render Tree

Render Tree には display: none の要素は含まれません(visibility: hidden は含まれます)。

セレクタのパフォーマンス

CSS セレクタは右から左へ評価されるため、右端がシンプルな方が高速です。

/* 遅い: すべての p に対して祖先を探す */
div.container div.content p { color: red; }

/* 高速 */
.content-text { color: red; }

ただし、現代ブラウザではほとんどのセレクタが十分高速なため、神経質になりすぎる必要はありません。

ステップ3: Layout(レイアウト)

各要素の位置とサイズを計算します。リフロー(Reflow)とも呼ばれます。

  • 要素の幅、高さ、位置
  • テキストの折り返し
  • 他の要素との関係

レイアウトは最も高コストなステップの一つです。特定のプロパティを変更すると再計算が走ります。

レイアウトを引き起こすプロパティ

width, height, padding, margin, border
top, left, right, bottom
font-size, font-family
display, position, float, overflow

これらを JavaScript で頻繁に変更するとパフォーマンスが悪化します。

ステップ4: Paint(ペイント)

各要素を実際のピクセルに変換します。

  • 背景色、画像
  • 文字
  • 枠線
  • シャドウ

ペイントだけ引き起こすプロパティ

color, background-color, background-image
border-color, outline-color
visibility, box-shadow, text-shadow

レイアウトは走らず、ペイントだけ発生するため、中程度のコストです。

ステップ5: Composite(コンポジット)

ペイントされた結果をレイヤーとして GPU に送り、合成して画面に表示します。

コンポジットだけで済むプロパティ

transform, opacity, filter

これらはメインスレッドを使わず GPU で処理されるため、最も高速です。60fps のアニメーションに最適です。

CSSトリガーマップ

プロパティ変更LayoutPaintComposite
width
background-color-
transform--
opacity--

結論: アニメーションには transformopacity を使うのがベストです。

悪い例: width でアニメーション

.bad {
  transition: width 0.3s;
}
.bad:hover {
  width: 200px; /* Layout → Paint → Composite */
}

良い例: transform でアニメーション

.good {
  transition: transform 0.3s;
}
.good:hover {
  transform: scaleX(2); /* Composite のみ */
}

Critical Rendering Path(CRP)

初回描画までの経路を CRP と呼びます。これを最短化するのがパフォーマンス最適化の鍵です。

HTML downloaded

DOM built

CSSOM built

Render Tree built

Layout

Paint

First Paint (FP)

First Contentful Paint (FCP)

Largest Contentful Paint (LCP)  ← Core Web Vitals

レンダリングブロッキングリソース

CSS

CSS はデフォルトでレンダリングをブロックします。理由は、スタイル未確定の状態で描画すると FOUC(Flash of Unstyled Content)が発生するからです。

対策: Critical CSS をインライン化し、残りを遅延読み込み。

<head>
  <style>
    /* Above the fold のスタイルをインラインで */
    .hero { ... }
  </style>
  <link
    rel="preload"
    href="/styles.css"
    as="style"
    onload="this.onload=null;this.rel='stylesheet'"
  />
</head>

JavaScript

<script> タグはデフォルトでパースをブロックします。前述の async / defer で解消できます。

リフローとリペイントの最適化

JavaScript でのバッチ処理

// 悪い例: 各行でリフローが発生
const el = document.getElementById('box');
el.style.width = '100px';
el.style.height = '100px';
el.style.padding = '10px';
// 良い例: 1回のリフローにまとめる
const el = document.getElementById('box');
el.style.cssText = 'width: 100px; height: 100px; padding: 10px;';

// または class で一括変更
el.classList.add('box-large');

レイアウトスラッシング(Layout Thrashing)

// 悪い例: 読み取り→書き込みの繰り返しで強制リフロー発生
for (let i = 0; i < items.length; i++) {
  const width = items[i].offsetWidth; // 読み取り
  items[i].style.width = (width * 2) + 'px'; // 書き込み
}
// 良い例: 読み取りと書き込みを分離
const widths = items.map(item => item.offsetWidth); // 読み取りバッチ
items.forEach((item, i) => {
  item.style.width = (widths[i] * 2) + 'px'; // 書き込みバッチ
});

will-change ヒント

ブラウザに「このプロパティが変更される」と事前に伝えることで、新しいコンポジットレイヤーを準備させられます。

.animated {
  will-change: transform;
}

注意: 乱用するとメモリ消費が増えます。アニメーション開始直前に付け、終了後に削除するのがベストプラクティスです。

element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});
element.addEventListener('mouseleave', () => {
  element.style.willChange = 'auto';
});

GPU レイヤーの作成条件

以下のいずれかでブラウザは独立した GPU レイヤーを作ります。

  • transform: translateZ(0) (ハック)
  • will-change: transform
  • video, canvas
  • opacity が 1 未満のアニメーション
  • filter プロパティ
  • position: fixed

測定と検証

Chrome DevTools の Performance パネルで、レンダリングプロセスを詳細に確認できます。

  1. DevTools を開く
  2. Performance パネル
  3. 録画開始 → 操作 → 停止
  4. 紫 = Rendering(Layout)、緑 = Painting、黄 = Scripting

Rendering タブで以下も可視化できます。

  • Paint flashing: ペイントが発生している箇所をハイライト
  • Layout Shift Regions: レイアウトシフトを可視化
  • Frame Rendering Stats: FPS とGPUメモリ表示

まとめ

ブラウザのレンダリングを理解すると、パフォーマンス改善の優先順位が明確になります。

重要なポイント:

  1. Layout > Paint > Composite の順にコストが高い
  2. transformopacity は最安
  3. CSS と JS はレンダリングをブロックする → 最適化必須
  4. DOM 操作はバッチ処理
  5. DevTools で実測

理論を知った上で、Lighthouse や Performance パネルで実測すれば、効果的に改善できます。次のステップは、Core Web Vitals の改善です。

参考リンク

#ブラウザ #レンダリング #パフォーマンス #CSS #JavaScript
シェア: