JavaScript | Web API:グラフィック・メディア - アニメーション処理

JavaScript JavaScript
スポンサーリンク

アニメーション処理は「毎フレーム、画面を描き直す仕組み」を作ること

グラフィック系の JavaScript でいう「アニメーション処理」は、
ざっくり言うと

「時間の経過に合わせて、
毎フレームごとに“今の状態”を画面に描き直す」

という仕組みをコードで作ることです。

ゲームのキャラクターが動く
グラフがにゅるっと伸びる
ローディングの丸がくるくる回る

こういうものは全部、「アニメーション処理」が裏で動いています。

HTML/CSS だけでもアニメーションはできますが、
Canvas や WebGL など「自分で描く系」の世界では、
JavaScript でアニメーションループを書くのが基本になります。


アニメーションの心臓部:requestAnimationFrame

setInterval ではなく requestAnimationFrame を使う理由

昔は setIntervalsetTimeout でアニメーションを書くことも多かったですが、
今は基本的に 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 の transitionanimation もよく使われます。

CSS アニメーションは、

ボタンをふわっとホバーさせる
モーダルをスッと出す
要素をスライドインさせる

といった「DOM 要素の見た目や位置」を動かすのが得意です。

一方、Canvas や WebGL を使った JavaScript アニメーションは、

ゲーム画面
物理シミュレーション
大量のパーティクル
カスタムグラフ

のように、「自分でピクセル単位で描く」タイプの表現に向いています。

どちらが偉いという話ではなく、

UI のちょっとした動き → CSS
ゲームやビジュアライゼーション → JavaScript + Canvas/WebGL

という住み分けで考えるとスッキリします。


初心者として「アニメーション処理」で本当に掴んでほしいこと

アニメーション処理の本質は、
「時間の経過に合わせて、状態を更新し、画面を描き直し続けること」 です。

そのうえで、まず次の感覚をしっかり押さえてください。

アニメーションループは requestAnimationFrame で作る。
動くものの「状態」(位置・速度など)を変数で持ち、毎フレーム少しずつ変える。
毎フレーム「消してから描き直す」のが Canvas アニメーションの基本。
timestampdelta を使うと「1 秒でどれだけ動くか」で考えられ、フレームレートに依存しない動きになる。

おすすめの練習は、次の順番です。

四角を 1 個、横に動かすだけのアニメーションを書く。
それを「時間ベースの更新(delta)」に書き換えてみる。
同じ仕組みで、丸を 10 個動かしてみる。

この 3 段階を自分の手で書けるようになると、
アニメーション処理はもう「難しそうな魔法」ではなく、
「状態と時間をちゃんと扱えば、好きな動きを作れるシンプルな仕組み」
として、かなり手触りよく感じられるようになっていきます。

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