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

第4回時間差をつけたトゥイーン

今回から、また新たなお題に取り組もう。CreateJSのサイトからネタを頂戴する。TweenJSのデモTWEEN CIRCLES図1⁠。同心円のリングが、ステージ上のクリックした位置に、時間差でトゥイーンする。TweenJSの応用例になる。

図1 ステージ上のクリックした位置に同心円のリングが時間差でトゥイーンする
図1 ステージ上のクリックした位置に同心円のリングが時間差でトゥイーンする

オブジェクトをステージ上のクリックした位置にトゥイーンする

トゥイーンの基本は前回のお題ですでに解説した。今回新たに覚えることは、ステージをクリックしたときのイベントとクリックした座標の取得だ。

第1のクリックは、ステージに置いたインスタンスに対してなら、DisplayObject.clickイベントで捉える。しかし、インスタンスのあるなしにかかわらず、ステージのどこをクリックしてもよいというときのイベントは、Stage.stagemouseup(文字列"stagemouseup")になる。

Stageオブジェクト.addEventListener("stagemouseup", リスナー関数);

マウスポインタのステージ上の座標も、Stageオブジェクトから調べられる。プロパティStage.mouseXStage.mouseYがそれだ。これだけわかれば前回までの知識と合わせて、取りあえずリングひとつを、ステージ上でクリックした位置にトゥイーンするアニメーションはつくれるだろう。

script要素全体は、後にコード1として掲げた。その中から、ステージでクリックした位置にトゥイーンする部分を次に抜き書きした。

StageオブジェクトのStage.stagemouseupイベントにリスナー関数(startTween())を加えている。そして、リスナー関数は、ステージ上のクリックされた座標をリングのインスタンス(circle)とともに、トゥイーンを定める関数(setTween())に引数として渡す。トゥイーンはTween.to()メソッドでインスタンスの位置をトゥイーンする。イージングにはEase.bounceOutを用いた。

なお、リングをつくる関数(createCircle())には、引数にリングの太さと色、および半径を渡して、戻り値はShapeインスタンスで受け取る。

var stage;
var circle;
function initialize() {
  // ...[中略:...
  circle = createCircle(10, "#113355", 20);
  // ...[中略:...
  stage.addChild(circle);
  stage.addEventListener("stagemouseup", startTween);
  createjs.Ticker.addEventListener("tick", stage);
}
function createCircle(stroke, color, radius) {
  // ...[中略:...
}
// ...[中略:...
function startTween(eventObject) {
  setTween(circle, stage.mouseX, stage.mouseY, 1500);
}
function setTween(target, nX, nY, duration) {
  createjs.Tween.get(target)
  .to({x:nX, y:nY}, duration, createjs.Ease.bounceOut);
}

さらに、お題の動きと同じように、始まりはステージ上のランダムなxy座標(nXとnY)から、リングを真ん中にトゥイーンさせた。

var stage;
var circle;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  var nWidth = canvasElement.width;
  var nHeight = canvasElement.height;
  var nX = Math.random() * nWidth;
  var nY = Math.random() * nHeight;
  circle = createCircle(10, "#113355", 20);
  setAppearance(circle, nX, nY);
  setTween(circle, nWidth / 2, nHeight / 2, 1500);
  // ...[中略:...
}
// ...[中略:...
function setAppearance(instance, nX, nY) {
  instance.x = nX;
  instance.y = nY;
}

これで、リングひとつを置いて、ステージ上のクリックした座標にトゥイーンアニメーションさせることができる図2⁠。script要素全体は、次のコード1のとおりだ。

図2 ステージをクリックした位置にリングがトゥイーンアニメーションする
図2 ステージをクリックした位置にリングがトゥイーンアニメーションする
コード1 オブジェクトひとつをステージ上のクリックした位置にトゥイーンさせる
<script src="http://code.createjs.com/easeljs-0.6.0.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.4.0.min.js"></script>
<script>
var stage;
var circle;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  var nWidth = canvasElement.width;
  var nHeight = canvasElement.height;
  var nX = Math.random() * nWidth;
  var nY = Math.random() * nHeight;
  circle = createCircle(10, "#113355", 20);
  setAppearance(circle, nX, nY);
  setTween(circle, nWidth / 2, nHeight / 2, 1500);
  stage.addChild(circle);
  stage.addEventListener("stagemouseup", startTween);

  createjs.Ticker.addEventListener("tick", stage);
}
function createCircle(stroke, color, radius) {
  var circle = new createjs.Shape();
  var myGraphics = circle.graphics;
  myGraphics.setStrokeStyle(stroke);
  myGraphics.beginStroke(color);
  myGraphics.drawCircle(0, 0, radius);
  return circle;
}
function setAppearance(instance, nX, nY) {
  instance.x = nX;
  instance.y = nY;
}
function startTween(eventObject) {
  setTween(circle, stage.mouseX, stage.mouseY, 1500);
}
function setTween(target, nX, nY, duration) {
  createjs.Tween.get(target)
  .to({x:nX, y:nY}, duration, createjs.Ease.bounceOut);
}

</script>

複数のオブジェクトを時間差でトゥイーンする

お題に向けてつぎに取り組むのは、同心円のリングをいくつもつくって、時間差でトゥイーンさせることだ。つくるインスタンスの数は、次のようにあらかじめ変数(circleCount)に定めておく。また、インスタンスを納める配列(circles)も用意しよう。

同心円のリングは、forループで中心から少しずつ半径を拡げてつくる。そのためには、リングをつくる関数(createCircle())に渡す半径の引数を、カウンタ変数(i)の値に比例して増やせばよい。

リングのインスタンスは配列(circles)に加える。そこで、トゥイーンを定める関数(startTween())は、配列からforループで取出したインスタンスにトゥイーンを与えてゆく。そのとき、トゥイーンにかける時間を、やはりカウンタ変数(i)に応じて延ばす。

// var circle;
var circles = [];
var circleCount = 20;
var delay = 1 / circleCount;
function initialize() {
  // ...[中略]...
  for (var i = 0; i < circleCount; i++) {
    var nX = Math.random() * nWidth;
    var nY = Math.random() * nHeight;
    // circle = createCircle(10, "#113355", 20);
    var circle = createCircle(10, "#113355", (i + 1) * 3);
    setAppearance(circle, nX, nY);
    // setTween(circle, nWidth / 2, nHeight / 2, 1500);
    setTween(circle, nWidth / 2, nHeight / 2, (0.5 + i * delay) * 1500);
    circles.push(circle);
    stage.addChild(circle);
  }
  // ...[中略]...
}
// ...[中略]...
function startTween(eventObject) {
  // setTween(circle, stage.mouseX, stage.mouseY, 1500);
  for (var i = 0; i < circleCount; i++) {
    var circle = circles[i];
    setTween(circle, stage.mouseX, stage.mouseY, (0.5 + i * delay) * 1500);
  }
}

これらの処理を加えれば、予め定めた数(circleCount)の同心円のリングがつくられ、トゥイーンの時間は大きいリングほど長くなって遅れる図3⁠。つまり、同心円のリングが時間差でトゥイーンアニメーションする。ただし、リングが単色なので、お題のようなキラキラ感がない。この効果はオブジェクト同士のカラー演算により与える。

図3 同心円のリングが外側になるほど遅れてトゥイーンする
図3 同心円のリングが外側になるほど遅れてトゥイーンする

DisplayObject.compositeOperationプロパティでイメージのピクセルを合成する

DisplayObjectインスタンスにはDisplayObject.compositeOperationプロパティが備わっていて、背後のオブジェクトのイメージとピクセルをどう合成するかが決められる[1]⁠。プロパティに"lighter"を定めると、Fireworksの[ブレンドモード]にある[加法]と同じで、オブジェクトの重なりはカラー値が加算されて明るくなる図4⁠。

図4 Fireworksの[ブレンドモード]で[加法]を選ぶと重なりが明るくなる
図4 Fireworksの[ブレンドモード]で[加法]を選ぶと重なりが明るくなる 図4 Fireworksの[ブレンドモード]で[加法]を選ぶと重なりが明るくなる

そこで、リングのインスタンスの表示を定める関数(setAppearance())の第4引数(composite)に、DisplayObject.compositeOperationプロパティが与えられるようにした。また、第3引数(nAlpha)には、インスタンスのDisplayObject.alphaプロパティの値も加えている。

この関数(setAppearance())は、初期設定の関数(initialize())の中のリングをつくるforループから呼び出す。第3引数のアルファ値は、カウンタ変数(i)の値が増えて半径の大きいリングほど値を下げた。第4引数の値は加法の"lighter"だ。

function initialize() {
  // ...[中略]...
  for (var i = 0; i < circleCount; i++) {
    // ...[中略]...
    // setAppearance(circle, nX, nY);
    setAppearance(circle, nX, nY, 1 - i * 0.025, "lighter");
    // ...[中略]...
  }
  // ...[中略]...
}
// ...[中略]...
// function setAppearance(instance, nX, nY) {
function setAppearance(instance, nX, nY, nAlpha, composite) {
  // ...[中略]...
  instance.alpha = nAlpha;
  instance.compositeOperation = composite;
}

これで、リングの重なりが明るく光るトゥイーンアニメーションになる図5⁠。手を加えたJavaScriptコードは、次にコード2として全体を掲げる。

図5 リングが重なり合うと明るく光る
図5 リングが重なり合うと明るく光る
コード2 トゥイーンアニメーションするリングをDisplayObject.compositeOperationプロパティで合成
var stage;
var circles = [];
var circleCount = 20;
var delay = 1 / circleCount;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  var nWidth = canvasElement.width;
  var nHeight = canvasElement.height;
  for (var i = 0; i < circleCount; i++) {
    var nX = Math.random() * nWidth;
    var nY = Math.random() * nHeight;
    var circle = createCircle(10, "#113355", (i + 1) * 3);
    setAppearance(circle, nX, nY, 1 - i * 0.025, "lighter");
    setTween(circle, nWidth / 2, nHeight / 2, (0.5 + i * delay) * 1500);
    circles.push(circle);
    stage.addChild(circle);
  }
  stage.addEventListener("stagemouseup", startTween);
  createjs.Ticker.addEventListener("tick", stage);
}
function createCircle(stroke, color, radius) {
  var circle = new createjs.Shape();
  var myGraphics = circle.graphics;
  myGraphics.setStrokeStyle(stroke);
  myGraphics.beginStroke(color);
  myGraphics.drawCircle(0, 0, radius);
  return circle;
}
function setAppearance(instance, nX, nY, nAlpha, composite) {
  instance.x = nX;
  instance.y = nY;
  instance.alpha = nAlpha;
  instance.compositeOperation = composite;
}
function startTween(eventObject) {
  for (var i = 0; i < circleCount; i++) {
    var circle = circles[i];
    setTween(circle, stage.mouseX, stage.mouseY, (0.5 + i * delay) * 1500);
  }
}
function setTween(target, nX, nY, duration) {
  createjs.Tween.get(target)
  .to({x:nX, y:nY}, duration, createjs.Ease.bounceOut);
}

Canvasの背景色をグレーにすれば、動きとしてはおおむねでき上がった。しかし、これで終わってはいけない。とくにインタラクションを含むコンテンツでは、いろいろ操作を試して、動きに問題か起きないか確かめなければならない。また、無駄な処理がないかの吟味も大切だ。

たとえば、Canvasのあちこちを続けざまにクリックしてみよう。同心円のリングが分断されてしまうはずだ図6⁠。これではアニメーションとして美しくない。次回は、こうしたところを直して仕上げたい。そういうことで、まだでき上がってはいないものの、参考までにここまでのコードをjsdo.itに上げた。

図6 Canvasのあちこちを続けざまにクリックするとリングのトゥイーンが分断される
図6 Canvasのあちこちを続けざまにクリックするとリングのトゥイーンが分断される

おすすめ記事

記事・ニュース一覧