こんにちは!フロントエンド・スペシャリストの私です。
日々、ブラウザという名の超高性能なバーチャル世界を相手に、いかに滑らかで美しい画面を描くか、エンジニアたちと熱い議論を交わしています。
さて、今回はWebブラウザの心臓部、そして私たちが作る滑らかなアニメーションの命運を握る「requestAnimationFrame(リクエスト・アニメーション・フレーム)」のお話です。
「JavaScriptで動きのあるものを作りたい!」そう思って最初にぶisky(ぶつかり)がちなのが、タイマー処理の定番である`setInterval`や`setTimeout`です。でも、これらを使ってアニメーションを動かすと、なんだかカクついたり、スマホのバッテリーが異常に減ったりした経験はありませんか?
「私のコードの書き方が悪いのかな…」と不安になる必要はまったくありません!大丈夫ですよ。それはあなたのせいではなく、ブラウザの「レンダリング(画面描画)の仕組み」を知らなかっただけなんです。
今回は、身近な例え話を交えながら、ブラウザとJavaScriptの呼吸をぴったり合わせる魔法のAPIの秘密を一緒に紐解いていきましょう。
—
1. ブラウザの描画は「パラパラマンガ」の世界
まず、Webブラウザが画面を私たちに見せる仕組みをイメージしてみましょう。
ブラウザの画面は、一枚のキャンバスに絵の具をずっと塗り続けているわけではありません。実は、ものすごいスピードで「パラパラマンガ」をめくっている状態です。
一般的なディスプレイは、1秒間に60回のペースで画面をパッと描き替えています。時間にして、約16.7ミリ秒に1回のペースです。この「1コマを描き替える瞬間」のことを、Web業界では「フレーム(画面更新サイクル)」と呼んでいます。
ここで、お買い物のレジを想像してみてください。
お客さん(JavaScriptの処理)が次々と商品を勝手にレジのベルトコンベアに放り投げたら、レジ打ちの店員さん(ブラウザの描画エンジン)はパニックになってしまいますよね。「ちょっと待って!今カゴの中を整理してるから!」と言いたくなるはずです。
従来の `setInterval` が引き起こす悲劇
これまで、アニメーションを作る際によく使われていた`setInterval(callback, 1000 / 60)`は、いわば「店員さんの都合はお構いなしに、16.7ミリ秒ごとに無理やり商品を投げ込むスパルタ方式」でした。
- タイミングのズレ: ブラウザが「よし、今から新しい1コマを描くぞ!」というタイミングと、JavaScriptが「動け!」と命令するタイミングがズレてしまう。
- 無駄な計算: ブラウザが描画を休んでいる間にもJavaScriptが動き続け、結果として描画されないはずのコマの計算までしてしまい、CPUを酷使してファンがブォーと回り出す。
- カクつき(フレームドロップ): 処理が間に合わず、コマが飛ばされて画面がカクカクする。
せっかくコードを書いたのに、これではブラウザも私たちもヘトヘトになってしまいますよね。
—
2. 救世主登場! `requestAnimationFrame` とは何か?
そこで登場するのが、今回の主役である`requestAnimationFrame(rAF)`です。
これはブラウザに対して、こうお願いするAPIです。
> 「ねえブラウザさん、次に画面を新しく描き替える(リフレッシュする)その直前に、私のお願い(アニメーションの処理)を一緒に実行してくれませんか?」
例えるなら、「店員さんがレジを打つタイミング(画面の更新サイクル)にぴったり合わせて、商品を一つずつ手渡しするスマートな仕組み」です。
これによって、以下のような素晴らしいメリットが生まれます。
1. 描画のタイミングと完全に同期する: 無駄な計算や描画の取りこぼしがなくなります。
2. 圧倒的に滑らか: 1秒間に60回のテンポ(高リフレッシュレートのディスプレイなら120回など)に綺麗に寄り添うため、バターのように滑らかなアニメーションになります。
3. タブが非アクティブの時はお休みしてくれる: ユーザーが別のタブに移動して画面が見えていない時、ブラウザは自動的にこの更新サイクルをスローダウン、または停止します。そのため、PCのバッテリーやCPUを無駄に食いつぶしません。 優しい世界ですね!
—
3. 実際に書いてみましょう:基本の使い方
百聞は一見にしかず。実際にコードを見てみましょう。
以下のコードをコピーして、ブラウザの開発者ツールのコンソールなどで動かしてみると、その素直な挙動に感動するはずです。
// 箱の要素をHTMLから取得したと仮定してください
const box = document.querySelector(‘.my-box’);
let currentPosition = 0; // 箱の今の位置
// アニメーションを動かす関数
function step() {
// 位置を少しずつ進める(1フレームにつき2ピクセル移動)
currentPosition += 2;
// 要素のスタイル(CSS)を書き換える
box.style.transform = `translateX(${currentPosition}px)`;
// もし画面の端に到達していなければ、次のフレームでもう一度自分自身を呼んでもらう
if (currentPosition < 300) {
// ★ここがポイント!ブラウザの次の描画タイミングに「次のお願い」を予約する
requestAnimationFrame(step);
}
}
// 最初の一歩を踏み出す(アニメーションのスタート)
requestAnimationFrame(step);
コードの解説と「お作法」
- `requestAnimationFrame(step)` は、一度実行されると「その回の画面更新が終わる直前」に、指定した関数(ここでは `step`)を1回だけ実行して終わります。
- ですから、アニメーションを続けたい場合は、関数の中の一番最後で、もう一度自分自身を`requestAnimationFrame`で予約し直すのがお決まりのパターン(再帰呼び出し)になります。
—
4. つまずきやすいポイントと、そっと寄り添うアドバイス
初学者の頃、私もここでよく悩みました。なので、あなたが同じところで迷わないように先回りしてシェアしておきますね。
Q. 「あれ? 動きのスピードがパソコンによって違う気がする…?」
A. はい、その通りです!気づけましたか?素晴らしい着眼点です。
先ほどのコードでは、「1回呼び出されるごとに2px進む」としていました。
- 1秒間に60回画面が更新されるPCでは、1秒間に `60 × 2 = 120px` 進みます。
- しかし、ゲーミングモニターなどで1秒間に120回画面が更新されるPC(120Hz環境)だと、1秒間に `120 × 2 = 240px` 進み、アニメーションが2倍速くなってしまいます。
【対策のヒント】
本格的なアニメーションを作る時は、「前回のフレームから何ミリ秒時間が経過したか(デルタタイム)」を計算に入れて移動量を掛け算する手法を使います。「今は少し時間が空いたから、多めに動こう」とブラウザの環境差を吸収できるようになると、あなたも立派なフロントエンド・アーキテクチャの仲間入りです!
—
5. まとめ:ブラウザと「対話」する心地よさ
今回は、`requestAnimationFrame` を通して、Webブラウザのレンダリングサイクルとの付き合い方を見てきました。
- ブラウザは常に一定のリズム(約16.7msごと)でパラパラマンガをめくっている。
- `setInterval` はそのリズムを無視して無理をさせる暴れ馬。
- `requestAnimationFrame` はそのリズムに優しく寄り添い、滑らかで省エネな体験を生み出す魔法の杖。
私たちが書くJavaScriptは、ただ画面を命令通りに動かすだけの独裁者ではありません。「ブラウザという相棒が今どんな状態なのか」を感じ取り、息を合わせてあげること。 それこそが、現代のWeb開発で私たちが大切にすべき、ちょっとした職人技であり、心地よいコードを書く秘訣です。
最初は難しく感じるかもしれませんが、動かしているうちに「あ、今ブラウザと呼吸が合ったな」と感じる瞬間が必ずやってきます。その時を楽しみにして、ぜひ今日のコードをあなたのエディタで試してみてくださいね。
それでは、また次の現場でお会いしましょう!

コメント