4. JavaScriptによるアニメーション

4.0 目的

ここでは Web ページにアニメーションを表示する。

アニメーションには、 一定時間おきにプログラムを実行するために、 タイマーを用いる。 タイマーとアニメーションの動作を理解しよう。

また、配列やオブジェクトも用いる。 アニメーションのプログラムを見ながら、 なぜプログラミングで配列やオブジェクトを使うかを考え、 これらの役割と、どう利用できるかを理解しよう。

サンプルプログラムは 1.3 サンプルコード に従って、 準備してあるものとする。 VS Code で chap04 フォルダーを開くと本章のサンプルプログラムが実行できる。

すでに 3. JavaScriptによる描画 についてはよく理解しているものとする。

4.1 最初のアニメーション

サンプルプログラム sample04_01 を起動してみよう。

このプログラムでは、20個の青い円が上から下に移動する。 面白い動きでしょう?

script.js を見ていこう。 index.html と stylesheet.css は sample03 と同じだ。

sample04_01/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// アニメーションで使用する変数
var c1;    // 2d 描画コンテキスト
var y0;    // アニメーションで増加していく数値

// 毎秒 30 回実行する関数
function tick1() {
  // 描画領域をいったんクリアする
  c1.clearRect(0, 0, 600, 600);

  // 20個の円についてのループ
  var n; // n 番目の円を表す変数
  var y1,y2; // y 座標の計算に用いる
  for (n = 0; n < 20; n++) {

    // n によって動き方を変化させる
    y1 = (n * 10); 
    y2 = (y1 + y0 * (n % 3 + 1)) % 590;

    // x座標 n*30+10, 高さ y1[n] に円を描画 
    c1.beginPath();
    c1.arc(n * 30 + 10, y2 + 10, 10, 0, Math.PI * 2);

    // 色は紺、影をつけて、円を塗りつぶす
    c1.fillStyle = 'rgb(0,128,255)'; // 紺色
    c1.shadowColor = 'rgb(0,0,0)';   // 影
    c1.shadowOffsetX = 5;
    c1.shadowOffsetY = 5;
    c1.shadowBlur = 5;
    c1.fill();
    
    // y0 を増加する
    y0++;

  }
}

// 初期化のための関数
function draw_canvas() {

  // c1 = 2d コンテキスト、を用意する
  var canv1 = document.getElementById('canvas_tag_1');
  c1 = canv1.getContext('2d');
  if (!canv1 || !canv1.getContext) {
      return false;
  }

  // y0 を初期化する
  y0 = 0;

  // tick1 を毎秒 30 回実行するよう設定する
  setInterval(tick1, 33);
}

行16, 行17でボールの位置を計算している。 ためしに行17に以下のように修正を加えて、変化を確認してみよう。

y2 = (y0 * (n % 3 + 1)) % 590;
y2 = Math.floor(y0 * n / 50 + 1) % 590;

動きが大きく変わることがわかる。

このようにプログラムによって、まったく異なる動きを作ることができる。

4.2 setInterval

アニメーションの仕組みを見ていこう。

このプログラムにはこれまでと同様に draw_canvas という関数があるが、 さらに、tick1 という関数が追加されている。

draw_canvas ではこれまでのような描画はおこなっていない。 以下のように y0 に 0 をセットして、 その後は setInterval という関数を呼び出すだけで終了する。

sample04_01/script.js
47
48
49
50
51
  // y0 を初期化する
  y0 = 0;

  // tick1 を毎秒 30 回実行するよう設定する
  setInterval(tick1, 33);

実際の描画はすべて、 この後で説明する tick1 という別の関数で行っているのである。

setInterval は interval すなわち、実行間隔を指定する。 最初の引数、tick1 が実行される関数。そして2番目の引数、 ここでは33が、tick1を実行する時間間隔を指定する。 時間間隔の単位は ms (ミリ秒)、すなわち1/1000秒単位である。

これによって、tick1 が 33ms 間隔で実行されるようになる。 だいたい毎秒30回弱、tick1 が実行される。 tick1 を一回実行するごとにアニメーションの1画面を表示する。 こうして少しづつ異なる画面を毎秒30回描画することで、 アニメーションを実現している。

次に tick1 の処理を見ていこう。

tick1 は毎秒30回実行される。 実行される毎にまず y0 を1 増やしている。

sample04_01/script.js
32
    y0++;

これにより、毎秒 30 回 y0 が増加する。

次に n 番目の円の y 座標を計算する。 y 座標は n と y0 によって変化するようになっている。

sample04_01/script.js
15
16
17
    // n によって動き方を変化させる
    y1 = (n * 10); 
    y2 = (y1 + y0 * (n % 3 + 1)) % 590;

この部分の計算によって y 座標は、左から何番目であるか、 何フレーム目であるか、 両方のパラメータによって変化するので、 円の高さは、場所、時間とともに変化する。 その結果このプログラムの実行画面のような動きが得られる。

行19~29は、前の単元で行った描画と同じである。

sample04_01/script.js
21
    c1.arc(n * 30 + 10, y2 + 10, 10, 0, Math.PI * 2);

のように x 座標が n * 30 + 10 y 座標が y2 + 10 の位置に円を描画している。

4.3 配列

sample04_03 ではこのようなアニメーションが表示される。 このようなアニメーションは配列を使うと作ることができる。

円がそれぞれ個別に上から下に移動していることが特徴だ。

script.js をみてみよう。

sample04_03/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// アニメーションで使用する変数
var c1;    // 2d 描画コンテキスト
var y1;    // Array(20) : 円の y 座標

// 毎秒 30 回実行する関数
function tick1() {
  // 描画領域をいったんクリアする
  c1.clearRect(0, 0, 600, 600);

  // 20個の円についてのループ
  var n;
  for (n = 0; n < 20; n++) {

    // x座標 n*30+10, 高さ y1[n] に円を描画 
    c1.beginPath();
    c1.arc(n * 30 + 10, y1[n] + 10, 10, 0, Math.PI * 2);

    // 色は紺、影をつけて、円を塗りつぶす
    c1.fillStyle = 'rgb(0,128,255)'; // 紺色
    c1.shadowColor = 'rgb(0,0,0)';   // 影
    c1.shadowOffsetX = 5;
    c1.shadowOffsetY = 5;
    c1.shadowBlur = 5;
    c1.fill();

    // y 座標を 10 増加し、もし580を超えたら
    // 画面の外に出てしまうので、0 に戻す
    y1[n] = y1[n] + 10;
    if (y1[n] > 580) {
        y1[n] = 0;
    }
  }
}

// 初期化のための関数
function draw_canvas() {
  // y1 に長さ20の配列を用意する
  y1 = new Array(20);

  // c1 = 2d コンテキスト、を用意する
  var canv1 = document.getElementById('canvas_tag_1');
  c1 = canv1.getContext('2d');
  if (!canv1 || !canv1.getContext) {
      return false;
  }

  // 20個の円のy座標をランダムに決める
  var n;
  for (n = 0; n < 20; n++) {
      y1[n] = Math.floor(Math.random() * 580);
  }

  // 以後 tick1 を毎秒 30 回実行する
  setInterval(tick1, 33);
}

配列は行38で作成されている。

sample04_01/script.js
37
38
  // y1 に長さ20の配列を用意する
  y1 = new Array(20);

ここで、長さ20の配列がつくられる。

y1 という変数は行3ですでに宣言されているが、 その時点では配列としては宣言されていない。 コメント中に Array(20) と書いてあるのは、 この変数名を配列に使うというコメントであって、 プログラムとしては意味を持たない。 JavaScript では配列をあらかじめ配列として宣言する必要がない。

y1 = new Array(20);

という代入で配列型のデータを作成し、代入している。 ここで初めて y1 に配列が代入される。

このプログラムでは y1 はサイズ 20 の配列としている。 この配列には 20 個の円それぞれの y 座標が記録されている。

それらの y 座標は以下でランダムに設定している。

sample04_03/script.js
47
48
49
50
51
  // 20個の円のy座標をランダムに決める
  var n;
  for (n = 0; n < 20; n++) {
      y1[n] = Math.floor(Math.random() * 580);
  }

ランダムな数は以下の式で生成している。

Math.floor(Math.random() * 580)

Math.random は 0 以上 1.0 未満の実数をランダムに生成する。 その数値を 580 倍し、Math.floor 関数で小数点以下を切り捨てている。 その結果, 0 ~ 579 までの整数がランダムに選ばれる。 したがって、 y1[0]からy1[19]に 0~579 のランダムな数が設定される。

sample04_03/script.js
26
27
28
29
30
31
    // y 座標を 10 増加し、もし580を超えたら
    // 画面の外に出てしまうので、0 に戻す
    y1[n] = y1[n] + 10;
    if (y1[n] > 580) {
        y1[n] = 0;
    }

ここで y 座標を 10 ずつ増加している。 これにより描画される円は1コマあたり10ピクセル移動していく。

しかし、ただ移動を続けると、 やがて画面で表示できる範囲を超えてしまう。 そこで 行29から行31までの if 文で、y座標が580を超えたら、 y座標を 0 に戻している。

こうして tick1 を 1回実行するたびに、y座標は 10 ずつ増加するが、 その位置に実際に描画する処理は、行14~行24で行っている。

sample04_03/script.js
14
15
16
17
18
19
20
21
22
23
24
    // x座標 n*30+10, 高さ y1[n] に円を描画 
    c1.beginPath();
    c1.arc(n * 30 + 10, y1[n] + 10, 10, 0, Math.PI * 2);

    // 色は紺、影をつけて、円を塗りつぶす
    c1.fillStyle = 'rgb(0,128,255)'; // 紺色
    c1.shadowColor = 'rgb(0,0,0)';   // 影
    c1.shadowOffsetX = 5;
    c1.shadowOffsetY = 5;
    c1.shadowBlur = 5;
    c1.fill();

このうち、円の位置と形状は行16で指定され、 行18以後は、 rgb による着色と、 shadow による影をつける処理を行っている。

ここで、 行28を以下のように修正して、どのように変化するかを確認しよう。

y1[n] = y1[n] + 1;
y1[n] = y1[n] + Math.floor(n / 2);
y1[n] = y1[n] + Math.floor(n % 3) + 1;

4.4 加速度

次に sample04_04 を起動してみよう。 以下の画面が表示される。

このプログラムでは加速度が表現されている。

プログラムを示す。

sample04_04/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// アニメーションで使用する変数
var c1;    // 2d 描画コンテキスト
var y1;    // Array(20) : y 座標
var vy;    // Array(20) : y 方向速度 

// 毎秒 30 回実行する関数
function tick1() {
  // 描画領域をいったんクリアする
  c1.clearRect(0, 0, 600, 600);

  // 20個の円についてのループ
  var n;
  for (n = 0; n < 20; n++) {

    // 円を描画
    // y 座標を整数に変換してから描画する    
    var y2;
    y2 = Math.floor(y1[n]);
    c1.beginPath();
    c1.arc(n * 30 + 10, y2 + 10, 10, 0, Math.PI * 2);

    // 着色と影
    c1.fillStyle = 'rgb(0,128,255)'; // 紺色
    c1.shadowColor = 'rgb(0,0,0)';   // 影
    c1.shadowOffsetX = 5;
    c1.shadowOffsetY = 5;
    c1.shadowBlur = 5;
    c1.fill();

    // y 座標を速度分変化させ
    // 580を超えたら速度を反転する
    y1[n] = y1[n] + vy[n];   // 速度分移動
    vy[n]++;                 // vy を 1 加算
    if (y1[n] > 580) {       // 580 で反射
        vy[n] = 1-vy[n];
    }
  }
}

// 初期化のための関数
function draw_canvas() {
  // y1 に長さ20の配列を用意する
  y1 = new Array(20);
  vy = new Array(20);
  // c1 = 2d コンテキスト、を用意する
  var canv1 = document.getElementById('canvas_tag_1');
  c1 = canv1.getContext('2d');
  if (!canv1 || !canv1.getContext) {
      return false;
  }

  // 20組のy座標とy方向速度を設定する
  // y座標は 50~200、速度は 0~5 の実数とする
  var n;
  for (n = 0; n < 20; n++) {
      y1[n] = Math.random() * 150 + 50; // y座標
      vy[n] = Math.random() * 5;        // 速度
  }

  // 以後 tick1 を毎秒 30 回実行する
  setInterval(tick1, 33);
}

加速度を表現するには、各円の位置と速度を変数で保持する必要がある。 そこでこのプログラムでは y1 を y座標の配列、 vy を y 方向の速度を保持する配列としている。

sample04_03/script.js
3
4
var y1;    // Array(20) : y 座標
var vy;    // Array(20) : y 方向速度 

初期値として、y座標は 50~200、速度は 0~5 の実数を設定している。

sample04_03/script.js
53
54
55
56
57
58
  // y座標は 50~200、速度は 0~5 の実数とする
  var n;
  for (n = 0; n < 20; n++) {
      y1[n] = Math.random() * 150 + 50; // y座標
      vy[n] = Math.random() * 5;        // 速度
  }

描画時には、まず y 座標を実数から整数に変換してから描画する。

sample04_04/script.js
15
16
17
18
19
20
    // 円を描画
    // y 座標を整数に変換してから描画する    
    var y2;
    y2 = Math.floor(y1[n]);
    c1.beginPath();
    c1.arc(n * 30 + 10, y2 + 10, 10, 0, Math.PI * 2);

そして y 座標は速度分変化させ、速度は毎回 +1 する。

sample04_04/script.js
32
33
    y1[n] = y1[n] + vy[n];   // 速度分移動
    vy[n]++;                 // vy を 1 加算

y 座標が 580 を超えた際には、y座標を0に戻す代わりに速度を反転させる。 これで、再び円は元の方向に戻っていくので、 あたかも反射したように見える。

sample04_04/script.js
34
35
36
    if (y1[n] > 580) {       // 580 で反射
        vy[n] = 1-vy[n];
    }

4.5 オブジェクト

次に sample04_05 を起動してみよう。 以下の画面が表示される。

このプログラムでは上下左右に ball が運動する。

プログラムを示す。

sample04_05/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// プログラム全体で使用する変数
var c1;        // 2d 描画コンテキスト
var balls;     // 全 ball を格納する配列
var number_of_balls; // ball の数

// ballのクラスの定義
// 初期化
function ball() {
  this.x = Math.random() * 570 + 10;
  this.y = Math.random() * 570 + 10;
  this.vx = Math.random() * 50 - 25;
  this.vy = Math.random() * 50 - 25;
}

// move : 移動
ball.prototype.move = function() {
  if (this.x < 10  || this.x > 580) {
    this.vx = - this.vx;
  }
  if (this.y < 10  || this.y > 580) {
    this.vy = - this.vy;
  }
  this.x += this.vx;
  this.y += this.vy;
}

// show : 表示
ball.prototype.show = function() {
  // 円を描画
    // y 座標を整数に変換してから描画する    
    var x1,y1;
    x1 = Math.floor(this.x);
    y1 = Math.floor(this.y);
    
    c1.beginPath();
    c1.arc(x1,y1, 10, 0, Math.PI * 2);
    c1.fillStyle = 'rgb(0,128,255)'; // 紺色
    c1.shadowColor = 'rgb(0,0,0)';   // 影
    c1.shadowOffsetX = 5;
    c1.shadowOffsetY = 5;
    c1.shadowBlur = 5;
    c1.fill();
}

// ball クラスを使ったアニメーションの本体
// 毎秒 30 回実行する関数
function tick1() {
  // 描画領域をいったんクリアする
  c1.clearRect(0, 0, 600, 600);

  // 20個の円についてのループ
  var n;
  for (n = 0; n < number_of_balls; n++) {
    // ball を移動し、描画する
    balls[n].move();
    balls[n].show();     
  }
}

// 初期化
function draw_canvas() {
  // c1 = 2d コンテキスト、を用意する
  var canv1 = document.getElementById('canvas_tag_1');
  c1 = canv1.getContext('2d');
  if (!canv1 || !canv1.getContext) {
      return false;
  }

  // ball数の設定
  number_of_balls = 20;

  // 全ballを格納する配列の準備
  balls = new Array(number_of_balls);
  var n;
  // 全ballの初期化
  for (n = 0; n < number_of_balls; n++) {
      balls[n]=new ball();
    }

  // tick1 を毎秒 30 回実行するための設定
  setInterval(tick1, 33);
}

このプログラムはオブジェクト指向のプログラムとなっている。 全体が大きく3つの領域に分かれている。

行1~行4ではプログラム全体で共有する変数を定義している。

sample04_04/script.js
1
2
3
4
// プログラム全体で使用する変数
var c1;        // 2d 描画コンテキスト
var balls;     // 全 ball を格納する配列
var number_of_balls; // ball の数

このように関数などに含まれない領域で変数を定義すると、 プログラム全体で同じ変数を共有することができる。

行6~行43では ball に関するすべての処理を記述している。 まず行7~行13で ball の初期化を行っている。

sample04_04/script.js
 6
 7
 8
 9
10
11
12
13
// ballのクラスの定義
// 初期化
function ball() {
  this.x = Math.random() * 570 + 10;
  this.y = Math.random() * 570 + 10;
  this.vx = Math.random() * 50 - 25;
  this.vy = Math.random() * 50 - 25;
}

ここで、:

this.x

という記法で、オブジェクトの属性を表す。 this.x はオブジェクト毎にその属性として固有の値を保持できる。 つまり ball 変数を2個作成すれば、それぞれ別の x の値を保持できる。 それぞれの属性は、以下の目的で用いる。

変数

意味

x

x座標

y

y座標

vx

x方向速度

vy

y方向速度

初期化ではx,y,vx,vy にそれぞれ乱数でランダムな値を設定する。

行15~行25では ball の動きを示す。

sample04_04/script.js
15
16
17
18
19
20
21
22
23
24
25
// move : 移動
ball.prototype.move = function() {
  if (this.x < 10  || this.x > 580) {
    this.vx = - this.vx;
  }
  if (this.y < 10  || this.y > 580) {
    this.vy = - this.vy;
  }
  this.x += this.vx;
  this.y += this.vy;
}

は、prototype を使って、既存の関数に moveと show を、 新たなメソッドとして追加している。

また、行27~行43 では ball の表示を行う。

表示に必要な x 座標、y座標は ball オブジェクトの属性として記録されている。 そのため宣言部だけはやや面倒であるが、show メソッド自体は比較的わかりやすいだろう。 このようにオブジェクト指向ではオブジェクトの動作を記述する場合には、 オブジェクトが表している「ただ一つの対象データ」のプログラムを書けばよい。

このように、オブジェクトを使うと、以下のメリットが得られる。

(1) 個々のオブジェクトに関するプログラムを書く場合に、 オブジェクトを使う側のプログラムのことは、 ほとんど考えないでプログラムできる。

(2) オブジェクトを使ってプログラムをする場合には、 各オブジェクトの内部のプログラムについては、 考えなくてよい。

4.6 ライブラリ

sample04_06 を起動してみよう。 以下の画面が表示される。

このプログラムではスプリングが3個動く。

プログラムを見てみよう。

sample04_06/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 使用するアニメーションのライブラリ anim_lib.js
//   anim_clearbg(2Dコンテキスト, x,y,w,h) : 背景を消去
//   anim_rect(2Dコンテキスト, 色,x,y,w,h) : 矩形
//   anim_spring(2Dコンテキスト, 色,x,y,段数,周波数,w,h) : 

// このアニメーションに登録する図形
var bg1;                     // 背景
var spring1,spring2,spring3; // 3つのスプリング
var rect1;                   // 支持棒

// 1 フレームの描画
function tick1() {
  // 背景
  bg1.show();
  // スプリング
  spring1.show();
  spring2.show();
  spring3.show();
  // 支持棒
  rect1.show();
 }

// 初期化
function initialize_page() {
  var c1;
  // 2D 描画コンテキスト c1 の準備
  sp_tcount = 0;
  var canv1 = document.getElementById('canvas_tag_1');
  c1 = canv1.getContext('2d');
  if (!canv1 || !canv1.getContext) {
      return false;
  }

  // アニメーション用のオブジェクトを設定する
  // 背景の消去領域の設定
  bg1=new anim_clearbg(c1,0,0,600,600);
  // 3 つのスプリングの設定
  spring1=new anim_spring(c1,"rgb(255,0,0)",50,50,4,1,100,200);
  spring2=new anim_spring(c1,"rgb(192,0,192)",250,50,4,2,60,100);
  spring3=new anim_spring(c1,"rgb(0,0,255)",400,50,6,0.5,100,400);
  // 支持棒の設定
  rect1=new anim_rect(c1,"rgb(64,64,128)",10,55,580,10);

  // インターバル設定 30フレーム/秒
	setInterval(tick1, 33);
}

予想より非常に簡単なプログラムだ。これでスプリングが動くのだろうか?

実はもう一つソースコードがある。

sample04_06/index.html
6
7
        <script src="anim_lib.js">    </script>       
        <script src="script.js">    </script>       

このように anim_lib.js という JavaScript のプログラムを読み込んでいる。

anim_lib.js をみてみよう。

sample04_06/anim_lib.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// アニメーションのライブラリ
// anim_clearbg(2Dコンテキスト, x,y,w,h) : 背景を消去
// anim_rect(2Dコンテキスト, 色,x,y,w,h) : 矩形
// anim_spring(2Dコンテキスト, 色,x,y,段数,周波数,w,h) : 


// class anim_clearbg の定義
function anim_clearbg (cc1,xx0,yy0,ww0,hh0) {
  this.c2=cc1; // 2D コンテキスト
  this.xx=xx0;
  this.yy=yy0;
  this.ww=ww0;
  this.hh=hh0;
}
// class anim_clearbg の show メソッド
anim_clearbg.prototype.show=
function(){
  var c1=this.c2;
  c1.clearRect(this.xx,this.yy,this.ww,this.hh);
}
// class anim_rect の定義
function anim_rect (cc1,cc0,xx0,yy0,ww0,hh0) {
  this.c2=cc1; // 2D コンテキスト
  this.cc=cc0; // 色
  this.xx=xx0;
  this.yy=yy0;
  this.ww=ww0;
  this.hh=hh0;
}
// class anim_rectの show メソッド
anim_rect.prototype.show=
function(){
  var c1=this.c2;
  c1.beginPath();
  c1.fillStyle = this.cc;
  c1.rect(this.xx,this.yy,this.ww,this.hh);
  c1.fill();
}

// class spring の定義
function anim_spring (cc1,cc0,xx0,yy0,nn0,ff0,ww0,hh0){
  this.c2 = cc1;// 2Dコンテキスト
  this.xx=xx0;  // x coordinate
  this.cc=cc0;  // 色
  this.yy=yy0;  // y 座標
  this.nn=nn0;  // number of stages
  this.ff=ff0;  // frequency
  this.ww=ww0;  // width of stages
  this.hh=hh0;  // height of spring
  this.tt=0;    // reset frame count
}

// class spring の show メソッド
anim_spring.prototype.show = 
function  () {
  var n,h1,h2,h3;  // ローカル変数
  var c1=this.c2;  // 色
  var x0=this.xx;  // x 座標
  var y0=this.yy;  // y 座標
  var n0=this.nn;  // スプリング段数
  var w0=this.ww;  // スプリング幅
  var h0=this.hh;  // スプリング長さ

  // フレームカウントを +1 する
  this.tt++;

  // スプリング1段の高さを計算
  l=120+Math.floor(Math.sin(this.tt*0.033*this.ff*2*Math.PI)*h0/n0);

  // スプリングを描画
  for (n=0; n<n0; n++) {
	  h1=y0+10+n*l/5;
	  h2=h1+l/5;
	  h3=h2+l/5;
	  c1.beginPath();
	  c1.moveTo(x0+w0,h1);
	  c1.bezierCurveTo(x0+2*w0,h1,x0+2*w0,h3,x0+w0,h3);
	  if (n<(n0-1)) {
      c1.moveTo(x0+w0,h3);
      c1.bezierCurveTo(x0,h3,x0,h2,x0+w0,h2);
	  }
    c1.lineWidth=10;
    c1.strokeStyle = "gray";
    c1.stroke();
  }

  // ボールを描画
  c1.beginPath();
  c1.arc(x0+w0, y0+66+l*(n0+1)/5, 60, 0, Math.PI * 2, true);
  c1.fillStyle = this.cc;
  c1.fill();
  c1.beginPath();
  c1.arc(x0+w0+20, y0+66+l*(n0+1)/5-30, 10, 0, Math.PI * 2, true);
  c1.fillStyle = "white";
  c1.fill();
}

ここでは、 4.5 オブジェクト で示した方法で、以下3つのクラスを定義している。

sample04_06/anim_lib.js
2
3
4
// anim_clearbg(2Dコンテキスト, x,y,w,h) : 背景を消去
// anim_rect(2Dコンテキスト, 色,x,y,w,h) : 矩形
// anim_spring(2Dコンテキスト, 色,x,y,段数,周波数,w,h) : 

script.js では行34-行42 でこのライブラリを使って、 画面に描画図形を設定している。

sample04_06/script.js
34
35
36
37
38
39
40
41
42
  // アニメーション用のオブジェクトを設定する
  // 背景の消去領域の設定
  bg1=new anim_clearbg(c1,0,0,600,600);
  // 3 つのスプリングの設定
  spring1=new anim_spring(c1,"rgb(255,0,0)",50,50,4,1,100,200);
  spring2=new anim_spring(c1,"rgb(192,0,192)",250,50,4,2,60,100);
  spring3=new anim_spring(c1,"rgb(0,0,255)",400,50,6,0.5,100,400);
  // 支持棒の設定
  rect1=new anim_rect(c1,"rgb(64,64,128)",10,55,580,10);

たとえば spring1 という変数は、 スプリングのアニメーションに必要な設定がされ、 2Dコンテクスト、ボールの色、x,y座標、スプリングの段数と振動周波数、 スプリングの幅と長さ、が設定される。

これらの図形に関数する情報は、 オブジェクトの内側に格納されるので、 描画の時には、 単にこれらのオブジェクトの、 show() メソッドを呼び出すだけで、 描画ができる。

そのため tick1 関数はとても簡単になってしまう。

sample04_06/script.js
11
12
13
14
15
16
17
18
19
20
21
// 1 フレームの描画
function tick1() {
  // 背景
  bg1.show();
  // スプリング
  spring1.show();
  spring2.show();
  spring3.show();
  // 支持棒
  rect1.show();
 }

たとえば spring1 の描画も、

spring1.show();

というメソッドの呼び出しによって、anim_lib内の

anim_spring.prototype.show

が呼ばれて、スプリングの描画が行われる。

このようにオブジェクト定義を使うと、 プログラムの部品化が可能になる。 メインプログラムでは部品化した機能を、 必要に応じて呼び出して、 利用することができる。

実はすでに多くのオブジェクトを使っていることがわかるだろうか?

たとえば、3.5.4 数値からrgbへ で、数値にたいして以下のように文字列に変換した。

(i * 18).toString(10);

これは数値というオブジェクトに対する、 メソッドとして準備されている機能である。

また、描画にたいして、

c1.rect(x,y,w,h);

という機能もまたメソッドである。

これらは HTML5 の JavaScript ではあらかじめ定義されているライブラリを使っているのである。

4.7 スコープ

ここで変数のスコープについて説明する。

これまで漠然と変数の働きを説明してきた。 たとえば、sample04_04/script.js では、 行3、行20、行28、行50に y1 という変数が現れる。 これらはすべて同じ「実体」を持つ。 つまりすべて同じ値を共有している。

行50で初期化した値が行20でも残っており、 行28で10増加することで描画位置が変化する。

しかし、sample04_05/script.js では 行9、17、23、32にthis.xという変数が出てくるが、 20個のballは別の場所に描画される。 行9では、その特定のballのthis.xの値が設定され、 行23では、その特定のballのthis.xにthis.vxが加算され、 他のballの this.x には影響はない。

たとえば太郎君の身長と次郎君の身長がそれぞれ異なるのは自然だが、 this.x というのはそれと同様に、 new ball() で新しいballが作成されるたびに、 その特定の ball が独自に this.x という値を持っている。

また sample04_06/script.js では、 行25で c1 という変数が定義されている。 c1は 2dコンテキスト である。 この変数はこれまでのサンプルプログラムでは、 プログラムの最初で定義されていた。 しかしそうすると、c1 という名前はプログラム全体で、 共有しなければならない。

しかしsample04_06では c1 は draw_canvas の中で定義されている。 したがってこの変数は、 draw_canvas の中でだけ同一の変数として扱われればいいので、 例えば、 draw_canvasの中でだけ c1 を ctx1 と書き換えても、 まったく問題なく動く。 そして、プログラムの他の部分で、 c1 という変数を他の目的で使っても、 全く問題がない。

こうしたことを、 スコープと呼ぶ。

スコープがあることで、 同じ変数を、プログラムの別の部分では問題なく使うことができ、 他プログラムの変数名を気にすることなく、 それぞれのプログラムで変数を使うことができる。

変数が出現する場所と、その有効範囲を以下に示す。

宣言された場所

有効な範囲

最外部

プログラム全域

関数名

プログラム全域

関数の仮引数

関数内部

関数内のvar宣言

関数内部

最外部で宣言された変数名だけが、 関数内で使われた場合に、広域変数(プログラム全体で共有される変数)として、 参照できる。 最外部と関数内で同じ変数名を使う場合、 もし関数内で仮引数に現れるか var で宣言された場合は、 関数内の独自の仮引数または変数として扱われる。 それ以外の場合は広域変数として処理される。

4.8 まとめ

  1. setInterval と canvas を使ってアニメーションを描画できる

  2. 多くの図形を独立に動かすには配列が役立つ

  3. 加速度を表すには2つの変数(配列)を使う

  4. オブジェクトを使うと、物体の動きを表すプログラムとそれらを制御するプログラムを、きれいに切り分けてプログラムを作ることができる。