Skip to content

3  アニメーションの基礎

3.1 描画の更新

描画の更新には、requestAnimationFrame() 関数を用います。 requestAnimationFrame() 関数は、ブラウザや環境に合わせて 60 fps の間隔で更新を行います。 引数には、繰り返し処理を行いたいコールバック関数を指定します。 更新を停止したい場合は、cancelAnimationFrame() 関数を呼び出します。
※ 本実習においてはアニメーションフラグによる更新の停止を行います(3.2 節参照)。

また、アニメーション表示をさせるためには、描画領域を一度リセットする必要があります。 描画領域のリセットには、clearRect() 関数を使うことができます。

clearRect(x, y, width, height);


clearRect() 関数によって指定した矩形領域は透明色で塗りつぶされ、すべての描画コンテンツが消去されます。


3.1.1 JavaScript コード例

Canvas に半径 20 の円を描画し、画面右下方向にアニメーションさせるコード例を以下に示します。 Canvas 要素の width, height 属性を利用することで、Canvas 領域全体のリセットを行っています。

3.2  アニメーション開始ボタンの設置

現在のアプリケーションは、ブラウザ上で読み込みが完了した時点でアニメーションが開始されます。 ユーザの好きなタイミングでアニメーションを開始できるよう、アニメーション開始ボタンを設置してみましょう。

3.2.1 ボタンの設置

まずは HTML 側で、ボタンを設置します。 今回は以下のように<input>タグを使うことにします。


type 属性を button とすることで、汎用ボタンとすることができます。
id 属性は btnAnime(JavaScript から参照するための名前)、
value 属性は アニメーション開始(ボタンに表示されるテキスト)、
onclick 属性は startAnime()(ボタンクリック時に実行する JavaScript の関数名)とします。


3.2.2 機能の実装

次に、ボタンを押した際の機能(startAnime() 関数)を実装します。 最も簡単な方法は、JavaScript コード読み込み時に draw() を実行せず、以下のように startAnime() 関数内で draw() を呼び出すことです。


ここでは何度も draw() が呼び出されることを防ぐため、 ボタンの disabled 属性を true にし、一度しか押せないようにしています。 ただし、これではボタンを押すまで何も表示されません。


3.2.3 ボタン機能の改良

ボタン機能に改良を加えていきましょう。具体的には、下記 2 点の機能を実装します。

  • ボタンを押すたびに再生と停止を繰り返す
  • ページ読み込み時に一度だけ draw() を呼び出す

まずは、アニメーションフラグ用変数(boolean: 真偽値型)を導入します。 変数名は anime、初期値は false とし、この変数が true のときのみアニメーションを実行するものとしましょう。 座標と描画の更新を行う部分を、if 文で囲えば良いことが容易にわかるかと思います。

次に、ボタンが押されるたびにアニメーションフラグ変数 anime の値を切り替える処理を実装します。 ボタン要素の value 属性を「停止」「再開」など切り替えてあげると、より親切なインタフェイスとなります。 あとは、アニメーションフラグ変数が true に切り替わった際に描画処理(draw())を呼び出してあげれば良いわけです。

最後に、ページ読み込み時に draw() を呼び出します。 アニメーションフラグ変数の初期値が false なので、一度だけ呼び出されるわけです。 上記をまとめると、以下のようなコードになります。

演習 8-1

演習 8-1

ボールのアニメーションおよび跳ね返りを実装しなさい。


3.3  ボールの跳ね返り

ボールの跳ね返り処理を実装してみましょう。

3.3.1 速度の導入

跳ね返りを実装するには、速度の概念を導入するのが望ましいです。 新たに各座標方向の移動量を表す変数 vx, vy を宣言し、1 フレームあたりの移動量を定義しましょう。 値は自由に決めて下さい。

// ボールの移動速度
let vx = 5;
let vy = 3;


3.3.2 跳ね返りの実装

先ほどはインクリメント演算子で 1 ずつ数値を加算していきましたが、 各座標に各速度成分を加算していくコードにします。 跳ね返り処理は、座標がキャンバスの外に出た際に、速度成分の正負を反転させることで実装可能です。

// 座標の更新
x += vx;
y += vy;

// 跳ね返りの処理
if (x < 0) {
  vx = -vx;
} else if (x > canvas.width) {
  vx = -vx;
}
if (y < 0) {
  vy = -vy;
} else if (y > canvas.height) {
  vy = -vy;
}


ただし、このコードでは跳ね返る前にボールが壁にめり込む挙動を示します。 ボールの半径を表す定数 RADIUS を用いて、ボールの経を考慮したコードに修正しましょう。 ここまでをまとめると、以下のようになります。


3.3.3 反発係数の導入

単純な反発係数の概念を導入してみましょう。 上では壁に接触した瞬間に速度成分を反転させるコードを書きましたが、 そこに反発係数 e (0 ≤ e ≤ 1) を乗じてあげれば良いことがわかります。 本来は壁に対して水平方向にも摩擦による速度減衰が生じますが、今回摩擦はないものとしましょう。 ソースコードは以下のようになります。


このコードを実行すると、ボールは画面右の壁で跳ね返らずに吸い付くような挙動を示します。 これは、ボールが壁にめり込んでしまい、減衰した速度では 1 フレームでボールが壁から離れられず、 跳ね返り処理の分岐から抜け出せないことが原因です。

少し乱暴ですが、以下のようにキャンバスの外に出たら壁の位置まで戻すコードを書いておきましょう。

3.4 複数のボールの作成

複数のボールを作成しましょう。

3.4.1 ボール情報のオブジェクト化

ボールが持つ座標情報と速度情報を、オブジェクト単位で管理していきます。 例えば、x = 10, y = 10, vx = 2, vy = 3 の属性を持つ ball オブジェクトは、以下のように宣言できます。

const ball = { x: 10, y: 10, vx: 2, vy: 3 };

オブジェクトの各属性を参照するには、ball.x のようにドット (.) を用います。


3.4.2 複数のボールの自動生成

複数のボールは配列で管理すると便利です。 配列は、let balls = []; のように書くことで生成することができます。 配列要素の追加には push() を用います。

50個のボールの座標と速度をランダムに決定し、ボールオブジェクトの配列を生成するコード例を以下に示します。

const balls = [];
const BALL_NUM = 50;
for (let i = 0; i < BALL_NUM; i++) {
  const tmpX = Math.floor(Math.random() * 501 + 50); // [50, 550]
  const tmpY = Math.floor(Math.random() * 301 + 50); // [50, 350]
  const tmpVX = Math.floor(Math.random() * 21 - 10); // [-10, 10]
  const tmpVY = Math.floor(Math.random() * 21 - 10); // [-10, 10]
  const ball = { x: tmpX, y: tmpY, vx: tmpVX, vy: tmpVY };
  balls.push(ball);
}


乱数の発生には、0 以上 1 未満の数値を返す Math.random() 関数を用います。 また、ここでは初期値として整数を指定するために、引数の数値を超えない最大の整数を返す Math.floor() 関数を利用しています。 Min 以上 Max 以下の整数値をランダムに生成したい場合は、Math.floor(Math.random() * (Max - Min + 1) + Min)とします。


3.4.3 複数のボールの描画

ボールオブジェクト配列の各要素の参照は、balls[0] のように角括弧([ ]) の内部に要素番号を指定して行います。 要素番号(添字)には変数を指定することができるので、for 文を用いた参照と描画が実行可能です。

複数のボールの描画を行うコード例を以下に示します。

See the Pen 3-4-3 by Toru Kano (@torukano) on CodePen.


3.4.4 異なるサイズのボール生成

ボールのサイズもランダムに決定するプログラムにしてみましょう。

ボールオブジェクトの要素として半径を表す r を追加し、同様の手順で実装することができます。 半径 1030 のボールをランダムに生成して描画するコードを以下に示します。 壁との接触判定は、各ボールの直径を参照しながら行います。

See the Pen 3-4-4 by Toru Kano (@torukano) on CodePen.


3.4.5 ボール色の設定 (RGB)

つぎに、ボールの色をランダムで決定する方法を紹介します。 ball オブジェクトのプロパティに color を追加し、その RGB (Red, Green, Blue) 値をランダムに決定するコード例を以下に示します。 String で 'rgb(R, G, B)' のように書くことで RGB 値から色を作成することができます。 例えば、'rgb(255, 0, 0)' とすると「赤」になります。

See the Pen 3-4-5 by Toru Kano (@torukano) on CodePen.


また、上記コードではボールの半径が初速度に影響を与えるように改変しています。
(ボールの半径が小さいほど初速度が大きくなる可能性を高くしています。)


3.4.6 ボール色の設定 (HSV)

上で紹介した色は、RGB 値をランダムに決定していましたが、 ある程度統一感のある色合いをランダムに得たい場合は、HSL 色空間を利用すると良いでしょう。 HLS 色空間は Hue(色相)、Saturation(彩度)、Lightness(輝度)で構成されており、 いずれかを固定しいずれかを変化させることで、統一感のある色を作ることができます。

css の hsl(色相, 彩度, 輝度) を利用し,色相のみをランダムに変化させた例を以下に示します。 淡く統一感のある色合いを生成できていることがわかります。

See the Pen 3-4-6 by Toru Kano (@torukano) on CodePen.