【実務・中級編】 ペイント(描画)の仕組み – Webブラウザの仕組み実践ガイド

やあ、調子はどうだい? 普段、何気なくCSSを書いてブラウザに綺麗な画面を表示させているけれど、「CSSのプロパティを1行書き換えただけで、なぜか画面がカクつく…」なんて経験はないかな?

「リフロー(Reflow)を避けて、リペイント(Repaint)に抑えれば速くなる」
これはフロントエンドの世界でよく言われる格言のようなものだ。しかし、そもそも「ペイント(描画)」の裏側で、ブラウザが一体どれほど泥臭く、天才的な泥仕事をしているかを本当に理解しているだろうか?

今回は、レンダリングパイプラインの中でも特に「ペイント(Paint)」に焦点を当て、ブラウザの内部挙動から、現場で即座に使えるパフォーマンス最適化のコードまで、シニアの視点から徹底的に解説するよ。

これを読めば、君が書く1行のCSSがブラウザの内部でどう処理されるのか、解像度が劇的に上がるはずだ。さあ、ブラウザの深淵を覗いてみよう。

—

1. 「ペイント(描画)」とは何か?:レイアウトとの決定的な違い

まず基本に立ち返ろう。DOMツリーとCSSOMツリーが合体して「レンダーツリー(Render Tree)」が作られ、各要素のサイズや位置(幾何学的な情報)が計算される。これがレイアウト(あるいはリフロー)だ。

レイアウトが終わった段階では、ブラウザはまだ「どこに、どれくらいの大きさの箱があるか」しか知らない。
この「箱」に対して、テキスト、色、画像、境界線(border)、影(box-shadow)などの視覚的な要素を、最終的に画面に表示する「ピクセル」のデータへと変換するプロセス、これが「ペイント(Paint)」だ。

ここで誤解してはならないのは、「ペイント処理=即座に画面に色を塗る」ではないということだ。

ブラウザ(例えばChromium)の内部では、ペイントは大きく以下の2つのステップに分かれている。

1. ペイント・レコーディング(Paint Recording)
2. ラスタライズ(Rasterization)

この2つのステップを詳しく見ていこう。

—

2. ブラウザの裏側:ディスプレイリストとスタッキングコンテキスト

ペイントの第1ステップである「ペイント・レコーディング」では、ブラウザは画面に直接絵を描くのではなく、「描画命令のリスト(ディスプレイリスト / Paint Record)」を作成する。

これは、いわば「ここに背景を描いて、次にその上に半径5pxの角丸で青いボタンを描いて、最後に白いテキストを重ねて描け」という指示書だ。

なぜ直接描かずに「指示書」を作るのか?

ウェブページには「重なり順(z-indexやスタッキングコンテキスト)」があるよね。手前から奥へ、正しい順番で描画命令を記録しておかないと、スクロールしたときや要素が重なったときに描画が破綻してしまう。

ブラウザは、この指示書(Paint Record)を作るために、レンダーツリーを走査し、スタッキングコンテキスト(積み重ね文脈)に従って描画命令を正しい順序に並べ替える。

第2のステップ:ラスタライズ(GPUへのバトンタッチ)

指示書ができあがったら、いよいよそれを実際のピクセル(ビットマップ画像)に変換する「ラスタライズ(Rasterization)」が行われる。

昔のブラウザはこれをCPUで愚直に行っていた。しかし現代のモダンブラウザは、この重いラスタライズ処理をGPU(グラフィックス・プロセッシング・ユニット)に任せている。これが「GPUアクセラレーション」だ。

1. CPU(レンダラープロセス)が描画命令(ディスプレイリスト)を作る。
2. そのデータをGPUプロセスに送る。
3. GPUが高速にピクセルデータへ変換(ラスタライズ)し、画面(フレームバッファ)に書き出す。

この一連の流れを理解しておくと、なぜ特定のCSSプロパティが重いのか、その理由がロジカルに説明できるようになる。

—

3. リペイント(Repaint)が引き起こす「パフォーマンスの崖」

では、実務で問題になる「リペイント」について話そう。
JavaScriptなどで要素のスタイル(例えば `background-color` や `color`、`box-shadow` など)を変更すると、要素のサイズ(レイアウト)は変わらないため、リフローはスキップされ、リペイントだけが発生する。

「リフローが起きないなら、軽くて安全だね!」と思うかもしれないが、それは大きな罠だ。

実は、`box-shadow` や `filter`(ぼかしなど)、`mix-blend-mode` を伴うリペイントは、CPUとGPUに甚大な負荷をかける。特に、ページ全体を覆うような巨大な要素や、スクロール時に常に再描画されるような要素でリペイントが発生すると、モバイル端末では一瞬でフレームレートが30fps以下に落ち、カクつき(Jank)が発生する。

DevToolsで「ペイントの発生」を可視化する

こればかりは、頭で考えているだけではダメだ。Chromeのデベロッパーツールを開いて、自分の目で見てみよう。

1. DevToolsを開き、`Cmd + Shift + P`(Windowsは`Ctrl + Shift + P`)を押す。
2. 「Rendering」と入力し、「Show Rendering」を選択。
3. 「Paint Flashing」にチェックを入れる。

これで、画面上でペイント(再描画)が発生したエリアが緑色にハイライトされる。もし、スクロールするたび、あるいはアニメーションするたびに画面の広範囲が緑色にチカチカ光るなら、それは「ペイントの崖」に片足を突っ込んでいる証拠だ。

—

4. 現場で使える!ペイントを回避する「コンポジット(合成)」への昇格

ペイントを最適化する究極の戦略は、「ペイント処理そのものをスキップし、コンポジット(合成)だけで済ませる」ことだ。

モダンブラウザには、特定の要素を「独立したレイヤー(層)」として切り出し、GPU側でそのレイヤーの位置や透明度だけを変化させる仕組みがある。これがコンポジット(Composite:レイヤー合成)だ。

レイヤーに昇格した要素の変更(位置移動や不透明度変更)は、レイアウトもペイントも発生させず、GPUがメモリ上の画像を動かすだけで済むため、圧倒的に軽い。

これを実現するために使うのが、`transform` と `opacity` だ。

比較コード:ダメな例 vs 劇的に軽いプロのコード

以下に、要素を横にスライドさせるアニメーションのコードを示す。
同じ見た目を実現するにも、ブラウザの裏側での処理は天と地ほどの差が出る。





ペイント最適化デモ


❌ 悪い例(毎フレーム、ペイントが発生してカクつく原因に)

⭕ 良い例(GPU合成により、ペイント0でヌルヌル動く)


なぜ `good-box` は速いのか?

`bad-box` の `left` を動かすと、ブラウザは周囲の要素への影響を再計算(レイアウト)し、さらに `box-shadow` を含めたピクセルデータを毎フレーム描き直す(ペイント)。

一方、`good-box` の `transform` は、ブラウザに「この要素は独立した1枚の絵(レイヤー)として扱ってくれ」と伝える。GPUはすでにメモリ上に描画済みの「箱の画像」を、ただスライドさせて重ね合わせる(コンポジット)だけだ。ペイント処理は最初の1回だけで、アニメーション中は完全に0回になる。

—

5. `will-change` の光と影:シニアからの警告

ここで、賢い君なら「すべての要素に `will-change: transform` や `will-change: opacity` をつければ最強じゃないか?」と思うかもしれない。

それは絶対にやってはいけない。

`will-change` は、ブラウザに対して「この要素は近いうちに変化するから、あらかじめメモリ(VRAM)を確保して独立したグラフィックレイヤーを作っておいてくれ」と頼む、いわば「特権の乱用」だ。

レイヤーを作るということは、その分、端末のグラフィックメモリを消費する。ページ内のすべての要素をレイヤーに昇格させると、モバイル端末ではメモリ不足(OOM: Out Of Memory)でブラウザがクラッシュしたり、逆に描画のオーバーヘッドで動作が極端に重くなったりする。

実務での `will-change` 運用ルール

1. 必要なときだけ動的に付与する: アニメーションが始まる直前にJSで付与し、終わったら削除する。
2. CSSで常時付与するのは、本当に常に動き続けるキー要素だけにする: 例えば、常に画面に追従するサイドバーや、常に動いているカルーセルなど。
3. 乱用厳禁: 1ページあたり数個程度に留めること。

—

まとめ:ブラウザに「描かせるな、重ねさせろ」

フロントエンドのレンダリング最適化において、ペイントの仕組みを理解することは、パフォーマンスの天井を突き破るための必須知識だ。

最後に、今回の要点をチームの共通認識として持ち帰ってほしい。

  • ペイントは「指示書(Paint Record)の作成」と「ラスタライズ(GPU処理)」の2段階で行われる。
  • `box-shadow`、`filter`、`background-blend-mode` などは、ペイントのコストが極めて高い。
  • アニメーションや動的な変化は、`left`/`top` ではなく `transform`/`opacity` を使い、ペイントをスキップして「コンポジット(合成)」で処理させる。
  • `will-change` は魔法の薬ではない。用法・用量を守って正しく使う。

「ブラウザにいかに効率よく、サボらせて描画させるか」
これができるようになったとき、君のフロントエンドの実力はもうワンランク上の次元へと到達する。

さあ、さっそく手元のプロジェクトで `Paint Flashing` を有効にして、無駄なペイントが起きていないかパトロールしてみよう! 何か気になる挙動があれば、いつでも相談に乗るよ。

コメント

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