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(略して 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 / setTimeoutrequestAnimationFrame結論:動き(見た目)を更新するなら rAF が基本、定期実行(通信や監視)なら setInterval が向く、みたいに使い分けるのが定番です。
初心者が最初にハマりやすいのがここです。
x += 1」だと、PCの性能や画面のリフレッシュ率(60Hz/120Hz)で速度が変わります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>
ポイント
time は「今の時刻」なので、前回との差分が deltaspeed を「秒あたり」にすると、環境が変わっても速度が安定しますrAF は「1回予約したら1回呼ばれる」仕組みなので、自分で再予約してループを作ります。
このとき一番ありがちな事故が 二重起動です。
requestAnimationFrame を何度も開始してしまう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;
}
アニメが重くなってきたら、次の発想が効きます。
特にDOMは、触り方で急に遅くなります。コツは:
offsetWidth などのレイアウト情報を読むのを避ける(レイアウトスラッシング)left/top より transform が有利なことが多い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(固定刻み)という定番があります。
rAF に合わせて好きなだけ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 は非表示タブで止まるか大幅に間引かれます。
「裏で動いてほしい監視処理」には向かないことがあります。
毎フレーム動かすほどバッテリーを食います。
“必要なときだけ動かす”設計が強いです。
アニメが苦手な人向けのOS設定があります。派手な動きは止める/弱めるのが親切です。
JavaScript
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!reduceMotion) {
requestAnimationFrame(tick);
}
performance.now() / Performanceパネル / console.time() で当たりを付けるrequestAnimationFrame(tick()) と書いて 即実行してしまう(正しくは関数を渡す)cancelAnimationFrame するIDを上書きしていて止まらないsetInterval と rAF を混ぜて、更新タイミングがぐちゃぐちゃになるrequestAnimationFrame は何に使うものですか?setInterval とどっちを使えばいいですか?requestAnimationFrame が基本です。一定間隔で通信・監視など「描画と関係ない定期処理」なら setInterval が向くことがあります。requestAnimationFrame が返すIDを保存して、cancelAnimationFrame(ID) を呼びます。二重起動を防ぐために running フラグを持つのが定番です。time 引数は何ですか?time との差分を delta time として使うと、環境差があっても動く速度を安定させられます。transform を使う、レイアウト情報の読み取り(offsetWidth など)を毎フレーム混ぜない、必要なときだけ動かす――が効果的です。setTimeout)を検討します。requestAnimationFrame(tick()) と書いてしまったrequestAnimationFrame(tick) にします。cancelAnimationFrame に渡すIDが違う/上書きしている可能性。開始時のIDを変数に保存し、running フラグも併用します。