HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う

第2回トゥイーンをランダムに定める

前回の第1回は、CreateJSのTweenJSライブラリでオブジェクトの座標をトゥイーンさせた。このトゥイーンにランダムな繰返しの動きを加えていく。

たが、その前にやらなければならないことがある。第1回が公開された2月14日(日本時間)に、CreateJSの新バージョンがリリースされた。その対応を先に済ませておきたい。

CreateJS新バージョンに対応した修正を加える

第1回には、コード2としてつぎのようなJavaScriptコードを書いた。このスクリプトに、CreateJS新バージョンに対応するための修正を加える。

第1回 コード2 時間と位置をランダムに定めてトゥイーンアニメーションする(再掲)
var stage;
var myBitmap;
var top = 0;
var bottom;
var right;
function initialize() {
  canvasObject = document.getElementById("myCanvas");
  var file = "images/Pen.png";
  var loader = new createjs.PreloadJS(false);
  right = canvasObject.width;
  bottom = canvasObject.height;
  stage = new createjs.Stage(canvasObject);
  loader.onFileLoad = draw;
  loader.loadFile(file);
}
function draw(eventObject) {
  var myImage = eventObject.result;
  var halfWidth = myImage.width / 2;
  var halfHeight = myImage.height / 2;
  top += halfHeight;
  bottom -= halfHeight;
  right -= halfWidth;
  myBitmap = new createjs.Bitmap(myImage);
  myBitmap.regX = halfWidth;
  myBitmap.regY = halfHeight;
  myBitmap.x = halfWidth;
  myBitmap.y = (top + bottom) / 2;
  stage.addChild(myBitmap);
  stage.update();
  setRandomTween(myBitmap);
  createjs.Ticker.addListener(window);
}
function setRandomTween(target) {
  var nextY = Math.random() * (bottom - top) + top;
  var nextPoint = new createjs.Point(right, nextY);
  var randomTime = Math.random() * 5000 + 1000;
  setTween(target, nextPoint, randomTime, createjs.Ease.bounceOut);
}
function setTween(target, myPoint, time, easing) {
  createjs.Tween.get(target)
  .to({x:myPoint.x, y:myPoint.y}, time, easing);
}
function tick() {
  stage.update();
}

まず、script要素に読込むCrateJSのJavaScriptファイルのバージョンが変わる。CreateJS CDN Librariesを見ると、EaselJSが0.6.0、PreloadJSは0.3.0、TweenJSは0.4.0と、いずれも0.1上がっている。

<script src="http://code.createjs.com/easeljs-0.6.0.min.js"></script>
<script src="http://code.createjs.com/preloadjs-0.3.0.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.4.0.min.js"></script>

第1回コード2のJavaScriptをそのままにして実行すると、動かない。PreloadJSライブラリの名前はそのまま変わらないものの、クラス名はPreloadJSからLoadQueueに変更されたからだ。このクラス名を変えれば、取りあえずはトゥイーンアニメーションが見られるようにはなる。

しかし、新CreateJSでは、イベントを扱う仕組みが新しくなった。前回はイベントにはハンドラを定めて扱うと解説した(⁠画像ファイルを読込んでCanvasに描く参照⁠⁠。このイベントハンドラも、当面は残されている。だから、上記の修正だけでも、アニメーションは動いた。

オブジェクト.イベント = ハンドラ

けれども、これからは「イベントリスナー」でイベントを扱うことが推奨されている。イベントリスナーの仕組みでは、addEventListener()メソッドでイベントに関数を登録する。この関数を「リスナー」と呼ぶ。なお、メソッドの第1引数には、イベント名を文字列で渡す。

オブジェクト.addEventListener(イベント名, リスナー)

すると、PreloadJSによる画像ファイルの読込みは、クラス名をLoadQueueに置き換えるだけでなく、読込み終えたときのイベント("fileload")に関数(draw())addEventListener()メソッドで加えることになる。

function initialize() {
  canvasObject = document.getElementById("myCanvas");
  var file = "images/Pen.png";
  // var loader = new createjs.PreloadJS(false);
  var loader = new createjs.LoadQueue(false);
  right = canvasObject.width;
  bottom = canvasObject.height;
  stage = new createjs.Stage(canvasObject);
  // loader.onFileLoad = draw;
  loader.addEventListener("fileload", draw);
  loader.loadFile(file);
}

前掲第1回コード2では、もうひとつイベントの扱いがある。Tickerクラスだ。Ticker.addListener()メソッドの替わりに、やはりTicker.addEventListener()メソッドで"tick"イベントにリスナー関数を加える。つまり、イベントはすべてaddEventListener()メソッドによりリスナーで扱うように統一されたのだ。

function draw(eventObject) {
  // createjs.Ticker.addListener(window);
  createjs.Ticker.addEventListener("tick", tick);
}
function tick() {
  stage.update();
}

上記のようにステートメントを書き替えれば、新CreateJSでトゥイーンアニメーションが正しく動く。ただ、ここでTickerのリスナー関数(tick())が行っているのは、Stage.update()メソッドの呼出しだけだ。このような場合にかぎっては、もっと簡単な書き方がある。

addEventListener()メソッドの第2引数には、オブジェクトを渡すこともできる[1]⁠。そして、Ticker.addEventListener()メソッドの第2引数にStageオブジェクトを渡せば、リスナー関数(tick())を定めなくても、Stage.update()メソッドが自動的に呼び出せる。これらの修正を加えたのが、つぎのコード1だ。改訂したjsdo.itのコードも添えた。

コード1 新CreateJSで時間と位置をランダムに定めてトゥイーンアニメーションする
var stage;
var myBitmap;
var top = 0;
var bottom;
var right;
function initialize() {
  canvasObject = document.getElementById("myCanvas");
  var file = "images/Pen.png";
  var loader = new createjs.LoadQueue(false);
  right = canvasObject.width;
  bottom = canvasObject.height;
  stage = new createjs.Stage(canvasObject);
  loader.addEventListener("fileload", draw);
  loader.loadFile(file);
}
function draw(eventObject) {
  var myImage = eventObject.result;
  var halfWidth = myImage.width / 2;
  var halfHeight = myImage.height / 2;
  top += halfHeight;
  bottom -= halfHeight;
  right -= halfWidth;
  myBitmap = new createjs.Bitmap(myImage);
  myBitmap.regX = halfWidth;
  myBitmap.regY = halfHeight;
  myBitmap.x = halfWidth;
  myBitmap.y = (top + bottom) / 2;
  stage.addChild(myBitmap);
  stage.update();
  setRandomTween(myBitmap);
  createjs.Ticker.addEventListener("tick", stage);
}
function setRandomTween(target) {
  var nextY = Math.random() * (bottom - top) + top;
  var nextPoint = new createjs.Point(right, nextY);
  var randomTime = Math.random() * 5000 + 1000;
  setTween(target, nextPoint, randomTime, createjs.Ease.bounceOut);
}
function setTween(target, myPoint, time, easing) {
  createjs.Tween.get(target)
  .to({x:myPoint.x, y:myPoint.y}, time, easing);
}

トゥイーンを繰返す

では、トゥイーンアニメーションに動きを加えよう。トゥイーンを繰り返してみたい。Tween.get()メソッドの第2引数にオブジェクトを渡し、そのloopプロパティにtrueを定めると、トゥイーンが繰り返される。だが、このアニメーションには不満を感じるだろう。

// createjs.Tween.get(target)
createjs.Tween.get(target, {loop:true})

前掲コード1は、トゥイーンにかける時間と移動先の垂直座標をランダムに定めた。確かに、ランダムな時間と位置にアニメーションする。けれど、Tween.get()メソッドに第2引数を渡して繰返されるのはその同じトゥイーンだ。つまり、ランダムな設定は初めに行われたら、その値のままトゥイーンが繰り返される。

繰返すたびにランダム値を変えるには、その設定の関数(setRandomTween())を改めて呼出さなければならない。Tween.call()メソッドは、関数の呼出しをトゥイーンに加える。

Tweenオブジェクト.call(関数, 引数の配列)

Tween.call()メソッドの第1引数に呼出す関数を定め、第2引数には関数に渡す引数を配列で渡す。配列に納めるのは、複数の引数が渡せるようにするためだ。つぎのようにTween.call()メソッドをドット.でトゥイーンに加えれば、ランダムに設定する関数(setRandomTween())が呼び出される。

function setTween(target, myPoint, time, easing) {
  createjs.Tween.get(target)
  .to({x:myPoint.x, y:myPoint.y}, time, easing)   // ;
  .call(setRandomTween, [target]);
}

もっとも、これでも望むアニメーションにはならないだろう。Tweenクラスのトゥイーンを定めるメソッドがドット.でつなげられたとき、後のメソッドのトゥイーンは前のトゥイーンが終わったところから続く。Tween.call()を呼び出す前のTween.to()メソッドで、トゥイーンするオブジェクトは右端にきている。そこから、Tween.call()メソッドでランダムな垂直位置を定めてトゥイーンすれば、右端で上下するだけのアニメーションになってしまう図1⁠。

図1 右端にトゥイーンした後垂直に上下する
図1 右端にトゥイーンした後垂直に上下する

そこで、トゥイーンは左右の端を行き来するアニメーションにしよう。

左右を行き来するランダムなトゥイーンアニメーション

トゥイーンの行き先は、関数でランダムに定めよう。関数(getNextPosition())には、トゥイーンを左右どちらの端から始めるのか引数(side)で渡す。すると、反対の端のランダムな座標をPointオブジェクトで返す。この関数はランダムなトゥイーンを定める関数(setRandomTween())から呼び出すことになる。

行き先座標を返す関数(getNextPosition())における垂直座標(nextY)の定め方は、前掲コード1と同じだ。引数(side)にはトゥイーンを始める端が文字列("left"または"right")で渡される。その値が左なら右端(right⁠⁠、そうでなければ左端(left)を水平座標(nextX)とする。そして、つぎのトゥイーンを始める端は、変数(currentSide)にとっておく。

var left = 0;
var currentSide;
// function setRandomTween(target) {
function setRandomTween(target, side) {
  // var nextY = Math.random() * (bottom - top) + top;
  // var nextPoint = new createjs.Point(right, nextY);
  var nextPoint = getNextPosition(side);
  // ...[中略]...
}
function getNextPosition(side) {
  var nextX;
  var nextY = Math.random() * (bottom - top) + top;
  if (side == "left") {
    currentSide = "right";
    nextX = right;
  } else {
    currentSide = "left";
    nextX = left;
  }
  return new createjs.Point(nextX, nextY);
}

なお、左端座標を新たに変数(left)として宣言した。また、ランダムなトゥイーンを定める関数(setRandomTween())には、第2引数にトゥイーンを始める端(side)が加わっている。

そこで、Tween.call()メソッドにも、呼び出す関数(setRandomTween())の新たな引数(currentSide)を加えなければならない。そして、画像ファイルを読込み終えたリスナー関数(draw())からは、ランダムなトゥイーン設定の関数(setRandomTween())を左端("left")から始めることにして呼び出す。

function draw(eventObject) {
  // ...[中略]...

  // setRandomTween(myBitmap);
  setRandomTween(myBitmap, "left");
  // ...[中略]...
}
function setTween(target, myPoint, time, easing) {
  createjs.Tween.get(target)
  .to({x:myPoint.x, y:myPoint.y}, time, easing)
  // .call(setRandomTween, [target]);
  .call(setRandomTween, [target, currentSide]);
}

これでオブジェクトは、左右にトゥイーンで行き来を繰り返すコード2⁠。そして、垂直位置とトゥイーンの時間は、毎回ランダムに変わる。

コード2 ランダムな垂直位置と時間で左右を行き来するトゥイーンアニメーション
var stage;
var myBitmap;
var top = 0;
var bottom;
var left = 0;
var right;
var currentSide;
function initialize() {
  canvasObject = document.getElementById("myCanvas");
  var file = "images/Pen.png";
  var loader = new createjs.LoadQueue(false);
  right = canvasObject.width;
  bottom = canvasObject.height;
  stage = new createjs.Stage(canvasObject);
  loader.addEventListener("fileload", draw);
  loader.loadFile(file);
}
function draw(eventObject) {
  var myImage = eventObject.result;
  var halfWidth = myImage.width / 2;
  var halfHeight = myImage.height / 2;
  top += halfHeight;
  bottom -= halfHeight;
  left += halfWidth;
  right -= halfWidth;
  myBitmap = new createjs.Bitmap(myImage);
  myBitmap.regX = halfWidth;
  myBitmap.regY = halfHeight;
  myBitmap.x = halfWidth;
  myBitmap.y = (top + bottom) / 2;
  stage.addChild(myBitmap);
  stage.update();
  setRandomTween(myBitmap, "left");
  createjs.Ticker.addEventListener("tick", stage);
}
function setRandomTween(target, side) {
  var nextPoint = getNextPosition(side);
  var randomTime = Math.random() * 5000 + 1000;
  setTween(target, nextPoint, randomTime, createjs.Ease.bounceOut);
}
function setTween(target, myPoint, time, easing) {
  createjs.Tween.get(target)
  .to({x:myPoint.x, y:myPoint.y}, time, easing)
  .call(setRandomTween, [target, currentSide]);
}
function getNextPosition(side) {
  var nextX;
  var nextY = Math.random() * (bottom - top) + top;
  if (side == "left") {
    currentSide = "right";
    nextX = right;
  } else {
    currentSide = "left";
    nextX = left;
  }
  return new createjs.Point(nextX, nextY);
}

なお、変数(left)に定める左端座標は、画像ファイルを読込み終えたリスナー関数(draw())で、オブジェクトがCanvasの外に切れないように幅の半分(halfWidth)内側に定めている図2⁠。ご参考までに、jsdo.itにも次のようにコードを掲げた。

図2 トゥイーンするオブジェクトがCanvasの外に切れないよう左端座標を内側に定める
図2 トゥイーンするオブジェクトがCanvasの外に切れないよう左端座標を内側に定める

次回は、いよいよお題を仕上げる。トゥイーンする端をCanvasの四辺に拡げ、イージングもランダムに切り替えよう。このお題についても、新CreateJS版を掲げておく。

おすすめ記事

記事・ニュース一覧