#04 ビジュアルコーディングを始めよう

requestAnimationFrameで動かす

これまで描いてきた図形はすべて「静止画」でした。今回は図形を動かす方法を学びます。

ブラウザのアニメーションには requestAnimationFrame? ブラウザの画面更新タイミング(通常1秒60回)に合わせて関数を繰り返し呼ぶAPI。アニメーションの標準実装方法で、`setInterval`より滑らかで省電力。 を使います。これを使いこなすと、コードが生き生きと動き出します。


requestAnimationFrame の仕組み

requestAnimationFrame(callback) は、ブラウザが次の画面更新(フレーム描画)のタイミングで callback 関数を呼んでくれるAPIです。

function draw() {
  // ここに描画処理を書く

  requestAnimationFrame(draw);  // 次のフレームでまた draw() を呼ぶ
}

draw();  // 最初の一回を手動で呼ぶ

requestAnimationFrame が自分自身を再登録することで、無限ループが形成されます。ブラウザのリフレッシュレートに合わせて動くため、通常は 1秒間に60回(60fps)呼ばれます。


時刻変数 t を使う

アニメーションでは「時間とともに変化する値」が必要です。最もシンプルな方法は、フレームごとに増える変数 t を使うことです。

var t = 0;

function draw() {
  // t を使って位置・色などを計算する
  var x = W / 2 + Math.cos(t) * 100;
  var y = H / 2 + Math.sin(t) * 100;

  t += 0.05;  // フレームごとに t を少し増やす
  requestAnimationFrame(draw);
}
draw();

tMath.cos(t)Math.sin(t) を掛けると、周期的に変化する値が得られます。これが滑らかなアニメーションの源泉です。


最初のアニメーション — 回転する点

まず最もシンプルなアニメーションから始めます。円周上を動く一点です。

ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);

var t = 0;

function draw() {
  // 背景を塗りつぶして前フレームをリセット
  ctx.fillStyle = '#0d1117';
  ctx.fillRect(0, 0, W, H);

  // t に応じた x, y を計算
  var x = W / 2 + Math.cos(t) * 100;
  var y = H / 2 + Math.sin(t) * 100;

  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI * 2);
  ctx.fillStyle = '#00e5ff';
  ctx.fill();

  t += 0.05;
  requestAnimationFrame(draw);
}

draw();
円周上を回転する点
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var t = 0;
function draw() {
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var x = W / 2 + Math.cos(t) * 100;
var y = H / 2 + Math.sin(t) * 100;
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + (t * 50) + ', 90%, 60%)';
ctx.fill();
t += 0.05;
requestAnimationFrame(draw);
}
draw();

残像効果 — 半透明で上書きする

毎フレームを完全に塗りつぶすのではなく、半透明で塗り重ねることで「残像」が生まれます。これはビジュアルコーディングの定番テクニックです。

// 完全に塗りつぶすのではなく、透明度を下げて上書きする
ctx.fillStyle = 'rgba(13, 17, 23, 0.15)';  // 背景色の半透明
ctx.fillRect(0, 0, W, H);

透明度(アルファ値)を 0.05〜0.3 の間で調整すると、残像の長さが変わります。

残像効果のある軌跡 — 半透明の上書き
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var t = 0;
function draw() {
ctx.fillStyle = 'rgba(13, 17, 23, 0.12)';
ctx.fillRect(0, 0, W, H);
var x = W / 2 + Math.cos(t) * 100;
var y = H / 2 + Math.sin(t * 1.7) * 80;
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + (t * 30) + ', 90%, 65%)';
ctx.fill();
t += 0.04;
requestAnimationFrame(draw);
}
draw();

x座標の t と y座標の t * 1.7 の周波数を変えると、リサジュー曲線のような軌跡が描かれます。


リサジュー曲線

x と y に異なる周波数の sin を使うと、複雑で美しい曲線が描けます。これをリサジュー曲線と言います。

var a = 3, b = 2;  // x方向とy方向の周波数

var x = W / 2 + Math.sin(a * t) * 110;
var y = H / 2 + Math.sin(b * t + Math.PI / 4) * 90;
リサジュー曲線 (a=3, b=2) が描かれるアニメーション
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var t = 0, a = 3, b = 2;
function draw() {
ctx.fillStyle = 'rgba(13, 17, 23, 0.04)';
ctx.fillRect(0, 0, W, H);
var x = W / 2 + Math.sin(a * t) * 110;
var y = H / 2 + Math.sin(b * t + Math.PI / 4) * 90;
ctx.beginPath();
ctx.arc(x, y, 2.5, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + (t * 20) + ', 90%, 65%)';
ctx.fill();
t += 0.015;
requestAnimationFrame(draw);
}
draw();

しばらく待つと、美しい図形が完成していきます。ab の比率を変えると異なる形が現れます。


パーティクルシステム

複数のオブジェクトをそれぞれ独立して動かすとパーティクルシステムになります。

// パーティクルの初期化
var particles = [];
for (var i = 0; i < 80; i++) {
  particles.push({
    x: Math.random() * W,
    y: Math.random() * H,
    vx: (Math.random() - 0.5) * 2,  // x速度
    vy: (Math.random() - 0.5) * 2,  // y速度
    hue: Math.random() * 360
  });
}

function draw() {
  ctx.fillStyle = 'rgba(13, 17, 23, 0.2)';
  ctx.fillRect(0, 0, W, H);

  particles.forEach(function(p) {
    p.x += p.vx;
    p.y += p.vy;
    // 壁で跳ね返る
    if (p.x < 0 || p.x > W) p.vx *= -1;
    if (p.y < 0 || p.y > H) p.vy *= -1;

    ctx.beginPath();
    ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
    ctx.fillStyle = 'hsl(' + p.hue + ', 90%, 60%)';
    ctx.fill();
  });

  requestAnimationFrame(draw);
}
壁で跳ね返るパーティクルシステム
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var ps = [];
for (var i = 0; i < 80; i++) {
ps.push({
  x: Math.random() * W, y: Math.random() * H,
  vx: (Math.random() - 0.5) * 2.5,
  vy: (Math.random() - 0.5) * 2.5,
  hue: Math.random() * 360
});
}
function draw() {
ctx.fillStyle = 'rgba(13,17,23,0.2)';
ctx.fillRect(0, 0, W, H);
ps.forEach(function(p) {
  p.x += p.vx; p.y += p.vy;
  if (p.x < 0 || p.x > W) p.vx *= -1;
  if (p.y < 0 || p.y > H) p.vy *= -1;
  p.hue = (p.hue + 0.3) % 360;
  ctx.beginPath();
  ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
  ctx.fillStyle = 'hsl(' + p.hue + ',90%,60%)';
  ctx.fill();
});
requestAnimationFrame(draw);
}
draw();

花火のように広がるパターン

中心から広がるパーティクルをt変数でコントロールします。

中心から放射状に広がるスターバースト
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var t = 0;
function draw() {
ctx.fillStyle = 'rgba(13,17,23,0.15)';
ctx.fillRect(0, 0, W, H);
for (var i = 0; i < 30; i++) {
  var a = (i / 30) * Math.PI * 2;
  var r = (t * 40) % 130;
  var x = W / 2 + Math.cos(a + t * 0.5) * r;
  var y = H / 2 + Math.sin(a + t * 0.5) * r;
  ctx.beginPath();
  ctx.arc(x, y, 4, 0, Math.PI * 2);
  ctx.fillStyle = 'hsl(' + (i * 12 + t * 60) + ',90%,60%)';
  ctx.fill();
}
t += 0.02;
requestAnimationFrame(draw);
}
draw();

アニメーションの定石パターン

var t = 0;  // 時間変数

function draw() {
  // ① 背景を塗る(全消去 or 残像)
  ctx.fillStyle = 'rgba(13, 17, 23, 0.2)';
  ctx.fillRect(0, 0, W, H);

  // ② t を使って何かを描く
  /* ... 描画処理 ... */

  // ③ t を進める
  t += 0.03;

  // ④ 次フレームを登録
  requestAnimationFrame(draw);
}

draw();  // 最初の呼び出し

この4ステップを覚えるだけで、どんなアニメーションでも作れます。

ステップ役割
① 背景を塗る前フレームをクリア or 残像を作る
② 描画するt に依存した図形・色を描く
t を進める毎フレームの変化量を決める
④ RAF登録次フレームへ繋ぐ

まとめ

この回でやったこと:

  • requestAnimationFrame? ブラウザの画面更新タイミング(通常1秒60回)に合わせて関数を繰り返し呼ぶAPI。アニメーションの標準実装方法で、`setInterval`より滑らかで省電力。 でアニメーションループを作った
  • 時刻変数 tMath.sin/cos で滑らかな動きを作った
  • 残像効果のための半透明上書きテクニックを学んだ
  • リサジュー曲線とパーティクルシステムを実装した

次回はいよいよワンライナーの世界に入ります。ここまで学んだすべてを凝縮して、数行〜1行で動く視覚芸術を作る方法を学びましょう。