おい、調子はどうだい?
今日も元気に `position: fixed;` のレイアウト崩れや、重いモーダルのカクつきと格闘していることだろう。
フロントエンドのパフォーマンスチューニングを語るとき、私たちはついつい「JavaScriptをいかに軽量化するか」「リフロー(Relayout)やリパイント(Repaint)をいかに減らすか」ばかりに囚われがちだ。もちろん、それは非常に正しいアプローチだよ。しかし、どれだけJSを最適化しても、ブラウザのレンダリングパイプラインの裏側で何が起きているかを理解していなければ、ユーザーがスクロールした瞬間の「あの嫌なカクつき」を根絶することはできない。
今回は、ブラウザの心臓部において「滑らかさの守護神」として君臨する、コンポジタースレッド(Compositor Thread)の役割と責務について、徹底的に解き明かしていこう。
—
1. なぜメインスレッドだけではスクロールがカクつくのか?
まずは、私たちが日々書いているコードが、ブラウザの内部でどう処理されているかの現実を直視しよう。
ブラウザの「メインスレッド(Main Thread)」は、いわば超多忙なプレイングマネージャーだ。
HTMLのパース、DOMやCSSOMの構築、JavaScriptの実行、イベントハンドラの処理、そしてスタイルの計算(Recalculate Style)やレイアウト(Layout)まで、すべてをたった一つのスレッドでこなそうとする。
ここに、フロントエンドエンジニアが陥りがちな「罠」がある。
ユーザーがページをスクロールしたとする。本来であれば、画面は60fps(あるいは120fps)で滑らかに追従してほしい。しかし、もしメインスレッド上で重いJavaScript(例えば、大量のDOM操作や複雑なデータ処理)が走っていたり、スクロールイベント(`scroll` イベントなど)に重い処理がバインドされていたりするとどうなるか?
メインスレッドは手一杯になり、新しいスクロール位置に応じた画面の描き換え処理(レイアウトやペイント)のスケジュールが遅延する。結果として、画面がピタッとフリーズしたように止まる、あの悪名高い「ジェクネス(Jank:カクつき)」が発生するのだ。
「いやいや、うちはReactもViteも使ってモダンにやってるから大丈夫だよ」なんて油断してはいけない。フレームワークがどれだけ洗練されていようとも、ブラウザの単一スレッドモデルの物理的な制約から逃れることはできないんだ。
—
2. コンポジタースレッドの登場:メインスレッドからの「独立」
この絶望的な状況を救うためにブラウザのアーキテクチャに組み込まれているのが、コンポジタースレッドだ。
近代的なブラウザ(ChromiumやGeckoなど)は、レンダリングの負荷を分散させるために、メインスレッドとは完全に独立した専用の「コンポジタースレッド」を持っている。
コンポジタースレッドの主な責務は以下の通りだ。
1. レイヤーの管理と合成(Compositing):
ブラウザがページをいくつかの「レイヤー(Layer)」に分割し、それをGPUを使って高速に重ね合わせる。
2. 入力の受け取りとビジュアルな追従:
ユーザーのタッチやスクロール操作を最初に検知し、メインスレッドの処理状況に関係なく、即座に画面をスクロール・ズームさせる。
特筆すべきは2番目のポイントだ。
コンポジタースレッドは、ユーザーがスクロール操作を行ったとき、メインスレッドのJS実行が終わるのを待たない。メインスレッドが重い処理でフリーズしていなかろうが構わず、「お、ユーザーがスクロールしたな。じゃあ、すでにメモリ上にあるレイヤーを少し下へ動かせばいいんだな」と判断し、GPUに指示を出して瞬時に画面を動かす。
これが、「メインスレッドがブロックされていても、スクロールだけはなぜかヌルヌル動く」という現象の正体だ。コンポジタースレッドが裏で泥臭く、かつ華麗にユーザー体験を死守してくれているおかげなのだよ。
—
3. コンポジタースレッドに仕事を奪わせる(=味方につける)極意
さて、ここからがシニアとしての腕の見せ所だ。
この優秀なコンポジタースレッドに「おい、ここは俺に任せろ!」と言わせるためには、メインスレッドをバイパスして、コンポジタースレッドだけで処理が完結するプロパティ(Compositor-only properties)を選んで使う必要がある。
具体的には、以下のプロパティの変更は、メインスレッドでのレイアウトやペイントを発生させず、コンポジタースレッドが直接GPU上で処理できる。
- `transform` ( `translate`, `scale`, `rotate` など)
- `opacity`
逆に言うと、`top`, `left`, `width`, `height`, `margin` などのプロパティをアニメーションさせたりスクロール連動させたりすると、ブラウザは「おっと、レイアウトからやり直さなきゃいけないな」と判断し、メインスレッドを呼び出してしまう。これがパフォーマンス低下の引き金になる。
実務で使える:コンポジタースレッドをフル活用するCSS/JSの書き方
では、実際に現場ですぐに使えるコードを見ていこう。
ここでは、モーダルやドロワーメニューを開閉する際、メインスレッドを極力汚さずに、コンポジタースレッドだけで滑らかなアニメーションを実現する実装例だ。
コンポジタースレッド最適化のデモ
下のボタンを押すと、メインスレッドに負担をかけない滑らかなドロワーが開きます。
—
4. デベロッパーツールでコンポジタースレッドの働きを暴く
「本当に自分のコードがコンポジタースレッドで処理されているのか?」それを自分の目で確かめずしてエンジニアとは言えないよね。Chrome DevToolsを開いて確認しよう。
1. Layersタブの活用:
DevToolsの「Command Menu」(Macなら `Cmd + Shift + P`)を開き、`Show Layers` と入力してレイヤーパネルを表示する。アニメーションする要素がちゃんと独立した「レイヤー」としてGPUに認識されているか確認できる。
2. Performanceタブでの検証:
パフォーマンス計測を行い、アニメーション中に `Recalculate Style` や `Layout` が毎フレーム発生していないかチェックする。もしアニメーション中にこれらが頻繁に起きていたら、それはコンポジタースレッドの恩恵を受け損ねている(メインスレッドが巻き込まれている)証拠だ。改善の余地あり、だね。
—
まとめ
Webブラウザは、私たちが想像している以上に裏側で泥臭い最適化をがんばってくれている。しかし、その恩恵を最大限に引き出せるかどうかは、フロントエンドを書く私たち次第だ。
- メインスレッドは超多忙なプレイングマネージャー。極力仕事を増やすな。
- スクロールやアニメーションは、`transform` と `opacity` を使ってコンポジタースレッド(とGPU)に丸投げしろ。
- `will-change` は諸刃の剣。ここぞという重いインタラクションに適切に貼っていこう。
このコンポジタースレッドの挙動を頭の中にスッと描けるようになると、パフォーマンスチューニングに対するアプローチがガラリと変わるはずだ。さあ、明日からのコードレビューで、誰よりも鋭く「おい、そのアニメーション、レイアウトシフト起きるから `transform` に書き換えようぜ」と指摘してやろうじゃないか。

コメント