【実務・中級編】 ペイントとコンポジットレイヤーの仕組み – Webブラウザの仕組み実践ガイド

やあ。今日もどこかのコードレビューで「アニメーションがカクつくから何とかしてくれ」って泣きついてこられたところかい?よくある話だよね。スタイルの崩れやメモリリークも厄介だけど、一番ユーザーにストレスを与えるのは、やっぱり画面の「ガタつき(Jank)」なんだ。

画面がスムーズに60fps(あるいは120fps)で動くかどうかは、DOMやCSSのコード量だけじゃなくて、「ブラウザが裏側でピクセルをどう描画し、どう重ね合わせているか」を理解しているかにかかっている。

今回は、レイアウトが終わった後の最後の関門、「ペイント(Paint)」と「コンポジット(Compositing:レイヤー合成)」の深淵について、実務で明日から使える知見を交えて徹底的に解説しよう。

—

1. そもそも「ペイント」と「コンポジット」で何が起きているのか?

DOMとCSSOMが合体した「レンダーツリー」ができあがり、それぞれの要素が「どこに配置されるか(レイアウト/リフロー)」が決まった後、ブラウザは本格的に画面を作る作業に入る。ここからが本番だ。

ペイント(Paint / Rasterization)の正体

ペイントとは、ざっくり言うと「要素を塗る作業」だ。
背景色、テキストの色、ボーダー、シャドウなど、「この要素はこういう見た目ですよ」という情報を、ピクセル単位ではなく「ここに線を描け、ここに塗りを入れろ」という描画命令のリスト(Display List)として生成する工程を指す。

そして、この描画命令を実際のビットマップ(ピクセルデータ)に変換する作業をラスタライズ(Rasterization)と呼ぶ。以前のブラウザはメインスレッドが全部これをやっていて大忙しだったけれど、現代のモダンブラウザ(BlinkやGecko)は、ここを複数のタイルに分割して、GPUのパワーを使って非同期で処理(ラスタライズ)している。

コンポジット(Compositing)の魔術

ペイントが終わったら、次はそれを画面に映し出す必要がある。ここで登場するのがコンポジット(レイヤー合成)だ。

Webページというのは、一枚のキャンバスにすべてを描いているわけじゃない。Photoshopのレイヤー構造を思い出してほしい。ブラウザは特定の条件を満たした要素を、メインのキャンバスとは別の独立した「レイヤー(Composite Layers)」として切り離し、それぞれ個別にペイント・ラスタライズさせる。

そして、最終的にそれらのレイヤーをGPU上で「ガッチャンコ」して一つの画面にする。これがコンポジットの仕組みだ。
なぜこんな面倒なことをするのか?答えはシンプルで、「一部のレイヤーだけを動かす(移動・拡大縮小・透明度変更)なら、下のレイヤーを再ペイントしなくていいから爆速になる」からに他ならない。

—

2. ブラウザの裏側:レイヤーが昇格(Promote)する条件

すべての要素が別レイヤーになると、GPUのメモリ(VRAM)がパンクしてしまう。だからブラウザは、デフォルトではドキュメント全体を基本的に一つのレイヤー(あるいはごく少数のレイヤー)として扱う。

しかし、特定のCSSプロパティや条件が指定されたとき、ブラウザは「おっ、こいつは激しく動く気だな?单独のレイヤーにしてやろう」と判断し、レイヤーを昇格(Promote)させる。これを実務では「ハードウェアアクセラレーションの有効化」と呼ぶこともあるね。

レイヤーが昇格する主なトリガーを知っておこう:

1. `transform: translate3d()`, `translateZ()`, `matrix3d()` などの3D・特定2Dトランスフォーム
2. `opacity` がアニメーションしている要素(JSやCSS transitions/animations経由)
3. `will-change` プロパティに `transform` や `opacity` が指定されている場合
4. `