【実務・中級編】 requestAnimationFrameとレンダリングサイクルの同期 – Webブラウザの仕組み実践ガイド

ブラウザの「呼吸」に合わせる: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アプリのパフォーマンスは一段階上のステージに引き上げられるはずだ。フロントエンドの仕事は、ピクセルを配置するだけじゃない。ブラウザという巨大な機械の「リズム」を整えることなんだよ。

さあ、エディタに戻って、君のコードを一段と滑らかにしよう。何か詰まったら、またいつでも聞いてくれ。

コメント

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