JavaScript

requestAnimationFrame lets you schedule “the next frame” at the same timing the screen is about to redraw, so you can make smooth animations or game loops while avoiding unnecessary updates.

requestAnimationFrame

requestAnimationFrame は、画面の描画タイミングに合わせて「次のコマ」を予約できるしくみで、ムダな更新を減らしつつ、なめらかな動き(アニメーションやゲームのループ)を作りやすくしてくれます。

requestAnimationFrame とは?

アニメーションって、パラパラ漫画みたいに「コマ(フレーム)」を素早く切り替えると動いて見えますよね。

requestAnimationFrame(略して rAF)は、その“次のコマ”を ブラウザにお願いして予約するための仕組みです。

つまり、スムーズに動かすのが得意で、ムダな更新を減らしやすいのがポイントです。

正確な説明

requestAnimationFrame(callback) は、ブラウザが次に画面を描画する前のタイミングで callback を呼ぶようにスケジュールします。

呼ばれた callback には引数として高精度な時刻(DOMHighResTimeStamp)が渡されます。

基本形はこれです。

JavaScript

let rafId = 0;

function tick(time) {
    // time はミリ秒(高精度): performance.now() に近い値
    rafId = requestAnimationFrame(tick);
}

rafId = requestAnimationFrame(tick);

止めたいときは cancelAnimationFrame(rafId)

setInterval と何が違うの?

setInterval / setTimeout
“時計”ベースで一定間隔を狙う
ただし描画タイミングとズレることがあり、カクつきやムダ更新が起きやすい
requestAnimationFrame
“描画”ベースで次のフレームに合わせる
アニメ用途で自然、タブ非表示時は間引かれて負荷が下がりやすい

結論:動き(見た目)を更新するなら rAF が基本、定期実行(通信や監視)なら setInterval が向く、みたいに使い分けるのが定番です。

いちばん大事:時間(delta time)で動かす

初心者が最初にハマりやすいのがここです。

こう書けばOK:delta time で位置を動かす(DOM版)

HTML

<div id="box"></div>

<style>
    #box {
        left: 0;
        top: 80px;
        width: 48px;
        height: 48px;
        background: #333;
        border-radius: 12px;
    }
</style>

<script>
	const container = document.getElementById("container");
	const box = document.getElementById("box");

	let rafId = 0;
	let lastTime = 0;
	let x = 0;
	const speed = 200; // px per second

	function tick(time) {
		if (!lastTime) lastTime = time;
		const deltaMs = time - lastTime;
		lastTime = time;

		const deltaSec = deltaMs / 1000;
		x += speed * deltaSec;

		// オブジェクトの端でループ
		let containerWidth = container.offsetWidth; // オブジェクトの幅
		let maxX = containerWidth - 64; // container.padding:16px + box.width:48px = 64
		if (x > maxX) x = 0;

		box.style.transform = `translateX(${x}px)`;

		rafId = requestAnimationFrame(tick);
	}

	rafId = requestAnimationFrame(tick);

	// 例:5秒後に止める
	setTimeout(() => {
		cancelAnimationFrame(rafId);
	}, 5000);
</script>

ポイント

“止め方”は設計そのもの(リーク・二重起動を防ぐ)

rAF は「1回予約したら1回呼ばれる」仕組みなので、自分で再予約してループを作ります。

このとき一番ありがちな事故が 二重起動です。

こう書けばOK:ガード付き start/stop

JavaScript

let rafId = 0;
let running = false;

function tick(time) {
    // ...更新処理...
    rafId = requestAnimationFrame(tick);
}

function start() {
    if (running) return;
    running = true;
    rafId = requestAnimationFrame(tick);
}

function stop() {
    if (!running) return;
    running = false;
    cancelAnimationFrame(rafId);
    rafId = 0;
}

“更新”と“描画”を分ける発想

アニメが重くなってきたら、次の発想が効きます。

更新(stateの計算)
物理・位置・アニメ進行度など
描画(render)
DOM反映、Canvas描画など

特にDOMは、触り方で急に遅くなります。コツは:

Canvasでの王道パターン(ゲームっぽいループ)

こう書けばOK:Canvas + delta time

HTML

<canvas id="c" width="280" height="140"></canvas>

<style>
	#c {
		background-color: #33333333;
	}
</style>

<script>
	const canvas = document.getElementById("c");
	const ctx = canvas.getContext("2d");

	let rafIdc = 0;
	let lastTimec = 0;

	const ball = {
		x: 40,
		y: 70,
		vx: 220 // px per second
	};

	function tick(time) {
		if (!lastTimec) lastTimec = time;
		const dt = (time - lastTimec) / 1000;
		lastTimec = time;

		// update
		ball.x += ball.vx * dt;
		if (ball.x > canvas.width - 16) {
			ball.x = canvas.width - 16;
			ball.vx *= -1;
		} else if (ball.x < 16) {
			ball.x = 16;
			ball.vx *= -1;
		}

		// render
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.arc(ball.x, ball.y, 16, 0, Math.PI * 2);
		ctx.fill();

		rafIdc = requestAnimationFrame(tick);
	}

	rafIdc = requestAnimationFrame(tick);
</script>

固定タイムステップ(物理がブレない)

rAF はフレーム間隔が毎回同じとは限りません。重い処理が挟まると dt (delta time) が大きくなります。

物理・当たり判定があると、dt が急に大きいと“すり抜け”や“爆速”が起きます。

そこで fixed timestep(固定刻み)という定番があります。

雰囲気サンプル(要点だけ)

JavaScript

let rafId = 0;
let last = 0;
let acc = 0;

const step = 1 / 60; // 60 updates per second
const maxSubSteps = 5;

function update(dt) {
    // 物理・ゲームロジックなど(dt は常に step)
}

function render(alpha) {
    // alpha を使うと補間(interpolation)もできる
}

function tick(time) {
    if (!last) last = time;
    let dt = (time - last) / 1000;
    last = time;

    // 変な飛びを抑える(タブ復帰直後など)
    if (dt > 0.25) dt = 0.25;

    acc += dt;

    let subSteps = 0;
    while (acc >= step && subSteps < maxSubSteps) {
        update(step);
        acc -= step;
        subSteps++;
    }

    const alpha = acc / step;
    render(alpha);

    rafId = requestAnimationFrame(tick);
}

rafId = requestAnimationFrame(tick);

タブ非表示・省電力・アクセシビリティ

タブが非表示のとき

多くのブラウザで rAF は非表示タブで止まるか大幅に間引かれます。

「裏で動いてほしい監視処理」には向かないことがあります。

省電力(モバイル)

毎フレーム動かすほどバッテリーを食います。

“必要なときだけ動かす”設計が強いです。

prefers-reduced-motion を尊重する

アニメが苦手な人向けのOS設定があります。派手な動きは止める/弱めるのが親切です。

JavaScript

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

if (!reduceMotion) {
    requestAnimationFrame(tick);
}

デバッグのコツ

フレームレートは目安
60fps前提にせず、dt で整える
二重起動チェック
running フラグ・ログ・ボタン無効化で防ぐ
重い原因の切り分け
  • DOM更新が多い?
  • レイアウト情報の読み取りが混ざってる?
  • 画像・Canvasが重い?
計測
performance.now() / Performanceパネル / console.time() で当たりを付ける

よくある落とし穴

よくある質問(FAQ)セクション

requestAnimationFrame は何に使うものですか?
画面が次に描画される直前のタイミングで処理を呼んでもらい、アニメーションやゲームループをなめらかに回すために使います。
setInterval とどっちを使えばいいですか?
見た目を毎フレーム更新したいなら requestAnimationFrame が基本です。一定間隔で通信・監視など「描画と関係ない定期処理」なら setInterval が向くことがあります。
どうやって止めますか?
requestAnimationFrame が返すIDを保存して、cancelAnimationFrame(ID) を呼びます。二重起動を防ぐために running フラグを持つのが定番です。
コールバックの time 引数は何ですか?
高精度な時刻(ミリ秒)です。前回の time との差分を delta time として使うと、環境差があっても動く速度を安定させられます。
タブを非表示にしたらどうなりますか?
多くのブラウザで止まるか大幅に間引かれます。裏で必ず動いてほしい処理には向かない場合があります。
重くならないコツはありますか?
毎フレームDOMを大量に触らない、位置は transform を使う、レイアウト情報の読み取り(offsetWidth など)を毎フレーム混ぜない、必要なときだけ動かす――が効果的です。

よくあるエラー早見表

TypeError: requestAnimationFrame is not a function
古い環境や特殊な実行環境の可能性。ブラウザ上で動かしているか確認し、必要ならフォールバック(setTimeout)を検討します。
requestAnimationFrame(tick()) と書いてしまった
関数を“呼ぶ”のではなく“渡す”のが正解です。requestAnimationFrame(tick) にします。
止めたはずなのに動き続ける
cancelAnimationFrame に渡すIDが違う/上書きしている可能性。開始時のIDを変数に保存し、running フラグも併用します。
動きが速すぎたり遅すぎたりする
フレーム数ベースで動かしている可能性。delta time(経過時間)で位置や進行度を計算します。
タブ復帰直後に一気にワープする
復帰直後は dt が大きくなることがあります。dt に上限を設ける(例: dt > 0.25 を丸める)と安定します。