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();
t に Math.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;
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(); しばらく待つと、美しい図形が完成していきます。a と b の比率を変えると異なる形が現れます。
パーティクルシステム
複数のオブジェクトをそれぞれ独立して動かすとパーティクルシステムになります。
// パーティクルの初期化
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`より滑らかで省電力。 でアニメーションループを作った
- 時刻変数
tとMath.sin/cosで滑らかな動きを作った - 残像効果のための半透明上書きテクニックを学んだ
- リサジュー曲線とパーティクルシステムを実装した
次回はいよいよワンライナーの世界に入ります。ここまで学んだすべてを凝縮して、数行〜1行で動く視覚芸術を作る方法を学びましょう。