2009-03-19

JavaScriptでタイマーイベントとcanvas要素を使う

一枚絵を描くだけじゃつまらない、と言われればなるほどごもっとも。やはり動きが見えてこそ、プログラムの甲斐があるという物でしょう。時間に沿った変化。それを実現する鍵になるのがタイマーです。

タイマーを使うには次の関数を使います。

  • setTimeout(f, t)
  • setInterval(f, t)

setTimeoutはtミリ秒経過後に一回だけ関数fを呼び出すよう、タイマーをセットします。

setIntervalはtミリ秒間隔で関数fを繰り返し呼び出すよう、タイマーをセットします。

まずはsetTimeoutから。

function f1()
{
  alert("f1呼ばれた");
}

setTimeout(f1, 1000);

1000ミリ秒後にメッセージを出します。実行は例によってインタラクティブコンソールで可能。

JavaScriptはコード中で自由に関数を作れるので、次のように書いても……

var f2 = function(){ alert("f2呼ばれた");}
setTimeout(f2, 1000);

次のように書いても……

setTimeout(function(){alert("f anonymous呼ばれた");}, 1000);

同じです。

これを使って絵を描いてみましょう。

var cv = document.createElement("canvas");
cv.setAttribute("width", "320");
cv.setAttribute("height", "240");
cv.style.cssText = "border: 1px solid;";
document.body.appendChild(cv);
var ctx = cv.getContext("2d");

setTimeout(drawLine, 50); //50ms後以降にdrawLineを呼び出すようセットする。

function drawLine()
{
  ctx.strokeStyle = "#" + Math.round(Math.random() * 0xffffff).toString(16); //色をランダムで決める。
  ctx.beginPath();
  ctx.moveTo(Math.random() * 320, Math.random() * 240); //始点をランダムで決める。
  ctx.lineTo(Math.random() * 320, Math.random() * 240); //終点をランダムで決める。
  ctx.stroke();
  setTimeout(drawLine, 50); //再度タイマーをセットする。
}

線が増えていく様子が確認できると思います。

setTimeoutは一回きりの呼び出しに使いますが、こうして呼び出されたときに再度タイマーを設定すれば、繰り返し呼び出すような用途にも使えます。

次はsetInterval。setIntervalは止めるまでずっと一定の間隔で関数を呼び出し続けようとします。

var cv = document.createElement("canvas");
cv.setAttribute("width", 120);
cv.setAttribute("height", 120);
cv.style.cssText = "border: 1px solid;";
document.body.appendChild(cv);
var ctx = cv.getContext("2d");

var second = 0;

setInterval(
  function(){
    // 秒をカウント。スコープの外のsecondを参照していることに注意。
    ++second;

    // キャンバスを透明色で塗りつぶす。
    ctx.clearRect(0, 0, 120, 120);

    // 円を描く。
    ctx.strokeStyle="#000";
    ctx.fillStyle="#fff";
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.arc(60, 60, 50, 0, Math.PI*2, false);
    ctx.fill();
    ctx.stroke();

    // 秒針を描く。
    ctx.strokeStyle="#f00";
    ctx.beginPath();
    ctx.moveTo(60, 60);
    ctx.lineTo(
      60+Math.sin(Math.PI * second / 30)*40,
      60-Math.cos(Math.PI * second / 30)*40);
    ctx.stroke();
  }
  , 1000);

setTimeoutで繰り返すと少しずつ実際の経過秒数とずれていってしまうのですが、setIntervalなら大丈夫です。