【実務・中級編】 requestAnimationFrameによる描画同期 – Webブラウザの仕組み実践ガイド

ブラウザの「心臓の鼓動」に合わせる:requestAnimationFrameが最強である理由

フロントエンドの現場で「画面がカクつく」「スクロールが重い」というバグに直面したとき、君たちはどうする?多くのエンジニアが真っ先に疑うのはJavaScriptの実行速度だ。しかし、往々にして犯人はそこじゃない。真犯人は、「ブラウザが描画しようとしているタイミングと、お前がDOMを書き換えようとするタイミングのミスマッチ」にある。

今日は、ブラウザレンダリングの深淵に触れつつ、`requestAnimationFrame` (rAF) を使った「正しい描画の作法」について、現場の知見を叩き込む。

—

ブラウザの描画パイプラインを理解する

まず、ブラウザが画面を更新する仕組みを正しく捉えておこう。ブラウザは基本的に、ディスプレイの更新頻度(一般的に60Hz = 16.6ms周期)に合わせて、以下のパイプラインを回している。

1. JavaScript: スクリプトを実行し、DOMを操作する。
2. Style: CSS計算(Recalculate Style)を行う。
3. Layout (Reflow): 要素のサイズや位置を計算する。
4. Paint: ピクセルを塗りつぶす。
5. Composite: 重ね合わせる(GPUの出番)。

問題は、`setTimeout` や `setInterval` でDOMを触ったときだ。これらはブラウザの描画サイクルを一切考慮しない。「今すぐ実行しろ」という命令は、運が悪ければ「ブラウザがまさに次のフレームの準備を始めた直後」に割り込むことになる。するとどうなるか? ブラウザは無理やり次のフレームで修正を反映させるために、余計なリフローやリペイントを強いられる。

これが「カクつき」の正体であり、ブラウザに無駄な計算を強いる「非効率な開発」の典型だ。

—

requestAnimationFrame:ブラウザとの「握手」

`requestAnimationFrame` は、いわばブラウザからの「次のフレームを描画する準備ができたよ!」という合図を待つためのAPIだ。これを使うと、ブラウザは「描画直前に実行すべきタスク」として君のコードをキューイングしてくれる。

実践:滑らかなアニメーションを実装する

例えば、要素を滑らかに移動させるコードを書くとき、`setInterval` を使ってはいけない。以下のように書くのがプロの流儀だ。

/

  • 要素を滑らかに動かすためのメインループ
  • @param {HTMLElement} element – 操作するDOM要素

/
function animate(element) {
let start = null;

function step(timestamp) {
if (!start) start = timestamp;
const progress = timestamp – start;

// 1. DOMの読み込み(ここで読み込みと書き込みを分けるのがコツ)
// 2. DOMの更新
// transformを使うことでLayoutやPaintをスキップし、Compositeだけで処理させる
element.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`;

// まだ終わっていないなら次のフレームを要求
if (progress < 2000) { requestAnimationFrame(step); } } // 初回呼び出し requestAnimationFrame(step); } ---

現場で陥る「強制リフロー」の罠

ここで一つ、シニアレベルの忠告をしておく。`requestAnimationFrame` を使っていても、書き方を間違えると台無しになる。それが「強制リフロー(Forced Synchronous Layout)」だ。

例えば、`rAF` の中で以下のようなコードを書いたとする。

requestAnimationFrame(() => {
// 1. スタイルを変更する(書き込み)
element.style.width = ‘100px’;

// 2. 高さを取得する(読み込み)
// ここで問題発生!ブラウザは最新の値を計算するために、
// 実行中のJavaScriptを止めてLayoutを強制的に発生させる
console.log(element.offsetHeight);
});

`style` を書き換えた直後に `offsetHeight` を参照すると、ブラウザは「ああ、今の状態じゃ正しい高さがわからないから、今すぐ計算しなきゃ!」と、本来なら次のフレームでまとめてやるべき計算をその場で実行する。これが重い。

ベストプラクティス:

  • 読み込みは読み込み、書き込みは書き込みでまとめる。
  • 可能な限り、スタイルの変更は一箇所に集約し、ブラウザの「バッチ処理」に任せる。

—

まとめ:なぜこれを意識するのか

「コードが動けばいい」という段階を卒業したなら、次は「ブラウザのリソースをいかに効率的に使うか」に目を向けるべきだ。

`requestAnimationFrame` を使うことは、単にアニメーションを滑らかにするためだけではない。ブラウザの描画エンジンという「巨大なエンジン」と呼吸を合わせるということだ。この感覚を身につければ、複雑なUIを構築しても、ユーザーにストレスを与えるような重いWebサイトを作ることはなくなるはずだ。

明日からの開発で、`setTimeout` を見かけたら、一度立ち止まって考えてみてほしい。「これは本当にこのタイミングで動かす必要があるのか? ブラウザの描画サイクルと同期できないか?」と。

それが、君が真のフロントエンド・スペシャリストへと進化するための第一歩だ。健闘を祈る。

コメント

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