ブラウザの「呼吸」に合わせる:requestAnimationFrameでレンダリングの深淵を制御する
やあ。今日もブラウザのレンダリングエンジンと格闘しているかい?
フロントエンドの世界に足を踏み入れると、誰もが一度は「カクつくアニメーション」や「妙に重いスクロールイベント」に頭を抱えることになる。JavaScriptの実行タイミングを適当に書いていると、ブラウザは「計算」と「描画」の板挟みになり、悲鳴を上げるんだ。
今日は、ブラウザのレンダリングサイクルを完璧に掌握するための切り札、`requestAnimationFrame`(以下、rAF)について、現場の視点から掘り下げていこう。
—
なぜ `setTimeout` ではいけないのか?
まず、根本的な事実を確認しよう。多くのブラウザは、現在 60fps(16.67ms周期) で画面を再描画するように設計されている。
`setTimeout(fn, 0)` や `setInterval` を使ってアニメーションを作った経験があるかもしれない。あれは極めて危険だ。なぜなら、それらはブラウザの描画タイミング(リフレッシュレート)とは無関係に実行されるからだ。
- 無駄な計算: 描画の更新が不要な瞬間にJavaScriptが走ってしまう。
- 重なり合い: 描画サイクルの合間に実行されず、結果としてフレームをスキップ(ドロップ)し、ユーザーには「カクつき」として映る。
一方、`rAF` は「次の描画準備ができる直前」に呼び出されるよう設計されている。つまり、ブラウザの「呼吸」に完全に同期するんだ。
ブラウザのレンダリングパイプラインを理解する
ブラウザのレンダリングプロセスを簡略化すると、以下のステップで進む。
1. JavaScript: ロジックの実行
2. Style: CSSの再計算
3. Layout: 要素の配置とサイズ計算
4. Paint: ピクセルの塗りつぶし
5. Composite: レイヤーの合成
`rAF` を使えば、「1. JavaScript」の段階で「次回のレンダリングに必要な計算」だけを済ませることができる。これにより、ブラウザは無駄な再計算をせず、スムーズに次のステップへ移行できるんだ。
—
【実践】rAFを使ったスマートなループ処理
現場でそのまま使える、基本の書き方を見てみよう。
// アニメーションの状態を管理する変数
let lastTime = 0;
let progress = 0;
function animate(timestamp) {
// timestampはブラウザが提供する高精度な実行時刻
if (!lastTime) lastTime = timestamp;
const deltaTime = timestamp – lastTime;
lastTime = timestamp;
// ここでDOM操作や計算を行う
// 重要なのは「ブラウザが描画する直前」に計算が完了していること
updateElementPosition(progress);
progress += 0.01;
if (progress < 1) { // 次のフレームでもう一度呼び出す requestAnimationFrame(animate); } } // 実行の開始 requestAnimationFrame(animate); function updateElementPosition(p) { const box = document.getElementById('box'); // トランスフォームを使うのは、LayoutやPaintをスキップして // Compositeだけで処理を完結させるための定石だ box.style.transform = `translateX(${p 100}px)`; }
プロのTips:強制同期レイアウト(Layout Thrashing)を避ける
中級者以上が陥りやすい罠が、「レイアウト・スラッシング」だ。
rAFの中で、`element.offsetWidth` のような「現在のレイアウト状態を読み取るプロパティ」を呼び出し、直後に `element.style.width` を書き換えるコードを書いていないか?
これをやると、ブラウザは「読み取るために強制的に計算(Layout)し、その直後に書き換えたからまた計算(Layout)する」という無駄なループを強いられる。
解決策はシンプルだ:
- 読み取り(Read):まとめて先に行う
- 書き込み(Write):まとめて後に行う
// 読み込みと書き込みを分離するパターン
requestAnimationFrame(() => {
// 1. Read: レイアウト情報の取得
const width = container.offsetWidth;
// 2. Write: スタイルの適用
// これを分離するだけで、無駄な再計算が劇的に減る
requestAnimationFrame(() => {
target.style.width = `${width / 2}px`;
});
});
—
まとめ:ブラウザと「対話」せよ
`rAF` は単なるアニメーション用APIではない。これは、「ブラウザのレンダリングエンジンに対して、今どのタイミングで処理を行うのが最も効率的か」を指示するための対話ツールだ。
1. フレームごとに同期させる: `setTimeout` は卒業しよう。
2. パイプラインを意識する: ReadとWriteを分ける。
3. Compositeを狙う: `transform` や `opacity` を優先し、Layoutを発生させない。
これらを意識するだけで、君が書くWebアプリのパフォーマンスは一段階上のステージに引き上げられるはずだ。フロントエンドの仕事は、ピクセルを配置するだけじゃない。ブラウザという巨大な機械の「リズム」を整えることなんだよ。
さあ、エディタに戻って、君のコードを一段と滑らかにしよう。何か詰まったら、またいつでも聞いてくれ。

コメント