アニメーション処理は「毎フレーム、画面を描き直す仕組み」を作ること
グラフィック系の JavaScript でいう「アニメーション処理」は、
ざっくり言うと
「時間の経過に合わせて、
毎フレームごとに“今の状態”を画面に描き直す」
という仕組みをコードで作ることです。
ゲームのキャラクターが動く
グラフがにゅるっと伸びる
ローディングの丸がくるくる回る
こういうものは全部、「アニメーション処理」が裏で動いています。
HTML/CSS だけでもアニメーションはできますが、
Canvas や WebGL など「自分で描く系」の世界では、
JavaScript でアニメーションループを書くのが基本になります。
アニメーションの心臓部:requestAnimationFrame
setInterval ではなく requestAnimationFrame を使う理由
昔は setInterval や setTimeout でアニメーションを書くことも多かったですが、
今は基本的に requestAnimationFrame を使います。
理由はシンプルで、
ブラウザの描画タイミングに合わせて呼んでくれる
タブが非表示のときは自動で間引いてくれる(無駄な CPU を使わない)
アニメーションが滑らかになりやすい
といったメリットがあるからです。
最小のパターンはこうです。
function loop() {
// ここで「状態の更新」と「描画」を行う
requestAnimationFrame(loop); // 次のフレームを予約
}
requestAnimationFrame(loop);
JavaScriptこの「自分で自分を呼び続ける」構造が、
アニメーションループの基本形です。
Canvas と組み合わせた最小アニメーション
HTML:
<canvas id="canvas" width="400" height="200" style="border:1px solid #ccc;"></canvas>
JavaScript:
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
let x = 0;
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "tomato";
ctx.fillRect(x, 80, 50, 50);
x += 2;
if (x > canvas.width) {
x = -50;
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
JavaScriptここでやっていることを日本語にすると、
毎フレーム、画面を一度全部消す
今の位置 x に四角を描く
x を少し進める(状態を更新する)
次のフレームを予約する
という流れです。
この「消して → 描いて → 状態を少し変えて → 次のフレーム」
というリズムが、アニメーション処理のど真ん中にあります。
状態と描画を分けて考えると一気に楽になる
「何がどう動いているか」は“状態”として変数に持つ
アニメーションで一番大事なのは、
「動いているものの状態を、ちゃんと変数で持つ」ことです。
例えば、丸が横に動くアニメーションなら、
丸の x 座標
丸の y 座標
丸の半径
丸の速度
などが「状態」です。
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
let x = 0;
const y = 100;
const radius = 20;
const speed = 3;
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = "skyblue";
ctx.fill();
x += speed;
if (x - radius > canvas.width) {
x = -radius;
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
JavaScriptここでのポイントは、
状態(x, y, radius, speed)は関数の外の変数で持つ
loop の中では「状態をもとに描く」+「状態を少し変える」だけをする
という構造になっていることです。
「状態」と「描画」を頭の中で分けて考えられるようになると、
アニメーションのコードが一気に整理されます。
描画処理は「今の状態を画面に反映するだけ」
さっきの例を、あえて分解して書くとこうなります。
function update() {
x += speed;
if (x - radius > canvas.width) {
x = -radius;
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = "skyblue";
ctx.fill();
}
function loop() {
update(); // 状態を更新
draw(); // 状態に基づいて描画
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
JavaScriptこのように、
update … 状態を変えるだけ
draw … 状態を画面に反映するだけ
と役割を分けると、
ゲームや複雑なアニメーションでも
頭の中で整理しやすくなります。
時間ベースのアニメーション(フレームレートに依存しない動き)
「1 フレームごとに 2px」ではなく「1 秒で 100px 動く」と考える
さきほどの例では、毎フレーム x += 2 のようにしていました。
これは「フレームごとに 2px 動く」という指定です。
でも、PC によってフレームレートが違うと、
速いマシンでは速く動き、遅いマシンでは遅く動いてしまいます。
そこで大事になるのが「経過時間(delta time)」を使う考え方です。
「1 秒で 100px 動く」と決めておいて、
実際のフレーム間の時間に応じて移動量を計算します。
requestAnimationFrame のタイムスタンプを使う
requestAnimationFrame は、
コールバックに「今の時刻(ミリ秒)」を渡してくれます。
これを使って、前フレームからの経過時間を求めます。
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
let x = 0;
const y = 100;
const radius = 20;
const speedPerSec = 100; // 1 秒あたり 100px 動く
let lastTime = null;
function loop(timestamp) {
if (lastTime === null) {
lastTime = timestamp;
}
const delta = (timestamp - lastTime) / 1000; // 秒に変換
lastTime = timestamp;
x += speedPerSec * delta;
if (x - radius > canvas.width) {
x = -radius;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = "tomato";
ctx.fill();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
JavaScriptここでの重要ポイントは、
timestamp は「このフレームの時刻(ms)」delta は「前のフレームから何秒経ったか」
移動量を speedPerSec * delta で計算している
というところです。
こうしておくと、
60fps でも 30fps でも、
「1 秒で 100px 動く」という体感は変わりません。
本格的なアニメーションやゲームでは、
この「時間ベースの更新」がほぼ必須になります。
複数のオブジェクトを動かすときの考え方
配列で「動くものたち」を管理する
画面に動くものが 1 個だけなら楽ですが、
実際には複数のオブジェクトが動くことが多いです。
例えば、丸が 10 個ふわふわ動くアニメーションを考えます。
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
const circles = [];
for (let i = 0; i < 10; i++) {
circles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: 10 + Math.random() * 20,
vx: -50 + Math.random() * 100, // -50〜50 px/s
vy: -50 + Math.random() * 100
});
}
let lastTime = null;
function loop(timestamp) {
if (lastTime === null) {
lastTime = timestamp;
}
const delta = (timestamp - lastTime) / 1000;
lastTime = timestamp;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const c of circles) {
c.x += c.vx * delta;
c.y += c.vy * delta;
if (c.x - c.radius < 0 || c.x + c.radius > canvas.width) {
c.vx *= -1;
}
if (c.y - c.radius < 0 || c.y + c.radius > canvas.height) {
c.vy *= -1;
}
ctx.beginPath();
ctx.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
ctx.fillStyle = "rgba(100, 150, 255, 0.7)";
ctx.fill();
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
JavaScriptここでの構造は、
1 個のオブジェクトを表す「状態の塊」をオブジェクトで持つ
それを配列に入れて、毎フレーム全員分を update & draw する
というものです。
「1 個動かすコード」を「配列に対してループするコード」に変えるだけで、
複数オブジェクトのアニメーションが作れます。
CSS アニメーションと JavaScript アニメーションの違い
「レイアウトを動かす」のか「ピクセルを自分で描く」のか
Web でアニメーションと言うと、
CSS の transition や animation もよく使われます。
CSS アニメーションは、
ボタンをふわっとホバーさせる
モーダルをスッと出す
要素をスライドインさせる
といった「DOM 要素の見た目や位置」を動かすのが得意です。
一方、Canvas や WebGL を使った JavaScript アニメーションは、
ゲーム画面
物理シミュレーション
大量のパーティクル
カスタムグラフ
のように、「自分でピクセル単位で描く」タイプの表現に向いています。
どちらが偉いという話ではなく、
UI のちょっとした動き → CSS
ゲームやビジュアライゼーション → JavaScript + Canvas/WebGL
という住み分けで考えるとスッキリします。
初心者として「アニメーション処理」で本当に掴んでほしいこと
アニメーション処理の本質は、
「時間の経過に合わせて、状態を更新し、画面を描き直し続けること」 です。
そのうえで、まず次の感覚をしっかり押さえてください。
アニメーションループは requestAnimationFrame で作る。
動くものの「状態」(位置・速度など)を変数で持ち、毎フレーム少しずつ変える。
毎フレーム「消してから描き直す」のが Canvas アニメーションの基本。timestamp と delta を使うと「1 秒でどれだけ動くか」で考えられ、フレームレートに依存しない動きになる。
おすすめの練習は、次の順番です。
四角を 1 個、横に動かすだけのアニメーションを書く。
それを「時間ベースの更新(delta)」に書き換えてみる。
同じ仕組みで、丸を 10 個動かしてみる。
この 3 段階を自分の手で書けるようになると、
アニメーション処理はもう「難しそうな魔法」ではなく、
「状態と時間をちゃんと扱えば、好きな動きを作れるシンプルな仕組み」
として、かなり手触りよく感じられるようになっていきます。
