【実務・中級編】 ペイント(Paint)とコンポジット(Composite)のプロセス – Webブラウザの仕組み実践ガイド

やあ、よく来てくれた。
フロントエンドの現場で「なぜかウチのWebアプリ、スマホでスクロールするとカクつくんだよね…」なんて頭を抱えていないかい?

CSSの書き方をちょっと変えるだけで見違えるようにヌルヌル動くようになることもあれば、逆に不用意なプロパティを1行足したせいで、GPUを盛大にイジメ上げてファンが唸るような重いページを作り上げてしまうこともある。この違いを分かっているかどうかが、君を「ただCSSを書く人」から「真のフロントエンド・アーキテクト」へと引き上げる分水嶺なんだ。

今回は、ブラウザの心臓部であるペイント(Paint)とコンポジット(Composite)のプロセスを徹底的に解剖していく。ブラウザが裏側でドットをどう描き、どう重ね合わせているのか。その泥臭い現実を一緒に覗いてみよう。

—

1. レンダリングパイプラインの全体像と「再計算」の恐怖

まずは、HTMLが画面のピクセルに変わるまでの大まかな旅路を思い出してほしい。

1. DOM / CSSOM 構築: HTMLとCSSをパースしてツリーを作る。
2. レイアウト(Reflow): 各要素が「どこに」「どれくらいのサイズで」配置されるかを計算する。
3. ペイント(Paint): 「この色で塗りつぶす」「この文字を描く」といった、ピクセル化の手前の描画命令(Display List)を作る。
4. コンポジット(Composite): 分割されたレイヤーを重ね合わせて、最終的な画面を一枚の絵としてGPUに送り出す。

現場で一番恐れられているのは、JavaScriptやCSSの変更によってこのパイプラインが上流から丸ごと再実行されることだ。特に、`width` や `top` などのレイアウトプロパティをいじると、レイアウト $\to$ ペイント $\to$ コンポジット の全行程(通称:Reflow/Repaint)が走り、メインスレッドが盛大にブロックされる。60fps(1フレームあたり16.6ms)の壁なんて一瞬で突破されてしまうのさ。

—

2. ペイント(Paint)プロセスの深掘り:画面の「下絵」を描く

レイアウトが終わると、ブラウザは「どの順番で、どうやって塗るか」のリスト(Display List)を作る。これがペイントだ。

ここで知っておくべき重要な事実がある。ペイントは、単一の巨大なキャンバスにすべてを描き込んでいるわけではないということだ。

ブラウザは、重なり順( stacking context )や、後述するレイヤーの条件に応じて、画面をいくつかのパーツ(ペイント・レコード)に分割して描画する。例えば、背景色、ボーダー、テキスト、シャドウなどがそれぞれのレイヤーに分けて描かれるイメージだ。

しかし、もし要素の `background-color` や `color` を変更すると、その要素が含まれる領域のペイントが走る。これだけでもメインスレッドのCPUリソースを食うのだが、さらに厄介なのは、「何を変更するとどこから再実行されるか」というブラウザの機嫌を取る必要が実務では常につきまとうことだ。

—

3. コンポジット(Composite)プロセスの魔法:GPUの力を借りる場所

さて、ここからが本題だ。ペイントで作られた個々のパーツ(レイヤー)を、最終的に画面という1枚の絵に合成する作業がコンポジット(Composite)だ。

現代のブラウザは非常に賢い。特定の条件を満たした要素を、メインスレッドとは別の独立した「コンポジットレイヤー(合成レイヤー)」としてGPUに丸投げする。
GPUにレイヤーを預けてしまえば、あとは位置の移動(`transform`)や透明度の変更(`opacity`)なんてお手のもの。CPUを煩わせることなく、GPUの超並列処理でグリグリと滑らかに動かせる。これが、いわゆる「ハードウェアアクセラレーション」の正体だ。

実務で使える:強制的にレイヤーを昇格させる「裏技」と注意点

「じゃあ、すべての要素をコンポジットレイヤーにしちまえ!」と思ったそこの君、ちょっと待ってくれ。
GPUのメモリは無限ではない。すべての要素をレイヤーに昇格させると、VRAMを食いつぶしてかえってブラウザがクラッシュしたり、初期化コストでパフォーマンスが低下する(これを「レイヤーの爆発 / Layer Explosion」と呼ぶ)。

だが、モーダルの開閉やスライドメニューなど、「絶対にカクつかせたくないアニメーション」には、意図的にコンポジットレイヤーを作らせるのがプロの定石だ。

ここで、CSSの出番になる。以下のコードを見てほしい。

/ 良い例:GPUレイヤーに昇格させ、ペイントとレイアウトをバイパスする /
.high-performance-modal {
/ will-changeでブラウザに「これからこのプロパティが変わるよ」と予告する /
will-change: transform, opacity;

/ レガシーブラウザ向けのハック(GPUレイヤー化のトリガー) /
transform: translateZ(0);
}

`will-change` プロパティや `transform: translateZ(0)`(または `translate3d(0,0,0)`)を使うことで、ブラウザはその要素をあらかじめ独立したコンポジットレイヤーとして扱う。その結果、アニメーション実行時に レイアウトもペイントもスキップされ、コンポジット(合成)だけで処理が完結する ため、驚異的な滑らかさを手に入れられるんだ。

—

4. 実践:レイアウト・ペイントを回避する「コンポジット専用」スタイリング

それでは、実務で明日から使えるコンポーネントのサンプルコードを見てみよう。
ドロワーメニューの開閉を想定したコードだ。CPUに優しい書き方と、そうでない書き方の違いを意識してほしい。





コンポジット最適化されたドロワーメニュー



このコードの何が優れているのか?

1. `left` や `width` を使っていない: もしここで `left: -300px` から `left: 0px` に変えるアニメーションを実装すると、フレームごとにレイアウト(Reflow)が走り、CPUが悲鳴を上げる。ここでは `transform: translateX()` を使っているため、レイアウトとペイントの工程が完全にスキップされ、GPUによるコンポジットのみでアニメーションが処理される。
2. `will-change: transform / opacity` の適切な指定: ブラウザに対して「この要素は動くぞ」と事前に伝えることで、無駄なレイヤー構築のタイムラグを防ぎ、最初の1フレーム目から滑らかに動作する。

—

5. チーフアーキテクトからの実践的アドバイス

実務でパフォーマンスチューニングを行う際の声掛けとして覚えておいてほしい。
「アニメーションさせるなら `transform` と `opacity` 以外は使うな、どうしても使うなら覚悟を持て」ということだ。

ブラウザのレンダリングパイプラインを頭の中にスラスラと思い描けるようになると、CSSを書いている最中に「あ、今のプロパティ変更はヤバい、レイアウトが走るな」と直感で分かるようになる。この感覚こそが、シニアエンジニアの武器だ。

さあ、今日のコードから無駄なリフローを引き起こしている犯人を探し出し、GPUのパワーを存分に引き出すスマートなコードに書き換えてやろうじゃないか。何か疑問があれば、いつでも質問してくれ。

コメント

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