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

第1回読み込んだ画像をトゥイーンアニメーションさせる

これからのWebはHTML5だ。とはいわれるものの、どの機能や技術を使うかは、コンテンツ次第でさまざまといえる。その中で、ダイナミックでインタラクティブな表現をつくりたいとき、Canvasの機能が注目される。ただし、Canvasが提供するのはおもに描画の機能で、アニメーションやインタラクションはJavaScriptライブラリの助けを借りることになる。

そこで本連載では、CreateJS Suiteを選んだ。開発者は、Flashコンテンツの世界で有名な Grant Skinner(グラント・スキナー)氏だ[1]⁠。⁠ダイナミックでインタラクティブな表現」ともって回った言い方をしたが、つまりは「Flashみないな」ものがつくりたい(つくってほしい)という要求だろう。CreateJSのライブラリ群は、Flashを使っていた人たちにとっては、なじみやすい設計になっている。

また、最近注目されている「Flashみたいな」表現の多くは、実際これまでFlashによる制作を手がけてきたクリエーターによるものが多い。あまりFlashに慣れていないという人も、その手法や考え方を学ぶ意味は大きい。さらに、CreateJSの開発にはAdobeとMicrosoftがスポンサーとして名を連ねていることも期待される。

ということで、本連載ではCanvasの機能をCreateJSで駆使して、⁠Flashみたいな」表現をつくってみようと思う。進め方としては、初めにお題となるコンテンツを挙げ、数回にわたってそのスクリプティングを解説する。それぞれのお題はアラカルトで、とくに互いの関連性は意識しない読み切りとなる。とはいえ、順序はやさしい基本的なものから、複雑あるいは他の技術や知識も使った応用的なものへと進む。引き続きのおつき合いをいただけるとありがたい。

お題:読込んだ画像をランダムにトゥイーンアニメーションさせる

早速、最初のお題をご紹介しよう。外部画像を読込んで、Canvasの四辺に向けてランダムにトゥイーンアニメーションさせる。トゥイーンのイージングや時間もランダムに変えている。jsdo.itに公開したので、コードもすぐにご覧いただける(つぎの[Play]ボタンで実行⁠⁠。ただし、jsdo.itに載せたコードと、本連載で説明するHTMLドキュメントのコードとは少し違う。また、連載で解説を進めていくうちにアイデアが出たり、気が変わって書替えたりもするので、あらかじめご理解いただきたい。

お題のjsdo.itのコードを見て、思ったより長いと感じてしまうかもしれない。CreateJSのライブラリを使えば、スクリプトが簡単になるのではないかと。だが、画像ファイルの読込みやトゥイーンのアニメーションそのものは、それぞれ数行のステートメントで済んでいる。

長いコードのほとんどは、設定をランダムに定める処理だ。どういうデータをもとに、どのようなランダムなアニメーションを見せるかは、つくり手が決めなければならない。逆にいえば、ファイルの読込みや設定にしたがったトゥイーンはライブラリに任せ、制作する者は表現を考えればよいということだ。

なお、連載を通して解説の進め方として、でき上がりのコードを丸のまま頭から読み下すことはしない。もっと単純化したサンプルから始め、それから順に機能や表現を加えていく。小さく始めることは、大きなコンテンツをつくるときに役立つし、大きな処理を関数に分けて考えることにもつながるからだ。とくに、スクリプトの初心者は、根気よく解説を読み進めてほしい。

画像ファイルを読込んでCanvasに描く

では、CreateJSのライブラリPreloadJSでPNG画像ファイルを読み込み、Canvasに描くところから始めよう。まだ動かない。PreloadJSライブラリを使う前に、準備を整える。

まず、script要素にライブラリを読込む。アニメーションはEaselJS、ファイルの読込みはPreloadJSが扱う。ここでは、CDN(Content Delivery Network)のライブラリを用いることにした[2]⁠。読み込みが速いし、サイトを移ってもキャッシュが効く。また、サーバーにライブラリを置かなくて済む。

<script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>
<script src="http://code.createjs.com/preloadjs-0.2.0.min.js"></script>

つぎに、body要素にcanvas要素を加える。そのid属性には、JavaScriptから参照するための名前("myCanvas")を定める。widthとheightの属性でCanvasの大きさが決まる。また、body要素のonload属性には、文書が読み込み終わったとき、初めに呼び出すJavaScriptの関数呼出し("initialize()")を書き加えておく。関数はこの後定める。

<body onLoad="initialize()">
  <canvas id="myCanvas" width="240" height="180"></canvas>
</body>

そして、EaselJSはCanvas上にステージ(Stage)というオブジェクトをつくり、そこにアニメーションを描く。JavaScriptでオブジェクトをつくるには、new演算子に続けてクラス(ここではStage)と同じ名前の「コンストラクタ」と呼ばれる関数を呼び出す。引数に渡すのは、canvas要素の参照だ。なお、CreateJSのクラスには、デフォルトでは「createjs」という名前を頭に添える(⁠名前空間と呼ばれる⁠⁠。

new createjs.Stage(canvas要素)

canvas要素の参照はJavaScriptのdocument.getElementById()メソッドで得られる。引数にはcanvas要素に定めたid属性の文字列を渡す。この処理は、初めに呼び出される初期設定の関数(initialize())に書き加える。これで準備は整った。

var stage;

function initialize() {
  canvasObject = document.getElementById("myCanvas");

  stage = new createjs.Stage(canvasObject);

}

PreloadJSライブラリでは、つぎの3つの手順で外部ファイルを読込む。

  1. PreloadJSオブジェクトをつくる
  2. ファイルが読み込み終わったとき呼び出す関数(ハンドラ)を定める
  3. ファイルを読込む

まず、PreloadJSオブジェクトは、JavaScriptのお約束どおりnew演算子とコンストラクタPreloadJS()でつくる。引数にはXMLHttpRequestを使うか(true⁠⁠、使わないか(false)をブール(論理)値で定める。今回はfalseを渡す。

PreloadJSオブジェクト = new createjs.PreloadJS(XMLHttpRequestの使用)

つぎに、ファイルが読み込み終わったとき呼び出したい関数を、ハンドラとしてAbstractLoader.onFileLoadイベントに定める[3]⁠。画像の大きさを調べたり、ステージに描画する処理は、読み込み終わってから行わなければならない。

PreloadJSオブジェクト.onFileLoad = ハンドラ

そして、ロードするファイルのURLをPreloadJS.loadFile()メソッドに渡して、読み込み始める。

PreloadJSオブジェクト.loadFile(ファイルのURL)

PreloadJSライブラリを用いた処理も、初期設定の関数(initialize())に書き加える。AbstractLoader.onFileLoadイベントで呼び出すハンドラの関数(draw())は、この後定める。

function initialize() {

  var file = "images/Pen.png";
  var loader = new createjs.PreloadJS(false);

  loader.onFileLoad = draw;
  loader.loadFile(file);
}

ファイルを読み込み終えたとき呼び出される関数(draw())は、読込んだ画像イメージをステージに描く。もっとも、画像イメージ(Imageオブジェクト)は、そのままではステージに表示できない。Bitmapクラスのオブジェクトに包んで、Stageオブジェクトの子として加えなければならないのだ。

まず、new演算子とBitmap()コンストラクタで、Bitmapオブジェクトをつくる。引数は読込んだ画像イメージのImageオブジェクトだ。Imageオブジェクトは、ハンドラの引数に渡されるイベントオブジェクトのresultプロパティから得られる。

Bitmapオブジェクト = new createjs.Bitmap(Imageオブジェクト)

つぎに、ステージに描くオブジェクトは、必ずStageオブジェクトの子に加えなければならない。そのためには、Stageオブジェクトに対してContainer.addChild()メソッドを呼び出し、子オブジェクトはその引数に渡す。

Stageオブジェクト.addChild(子オブジェクト)

そして、忘れていけないのが、ステージを再描画するStage.update()メソッドの呼出しだ。Canvasそのものはアニメーションを考えていないので、命じられないかぎり画面は描き変えない。EaselJSはアニメーションのため一定の時間間隔で繰返し処理する仕組みをもつものの、おそらく無駄な負荷を省くため、画面はStage.update()メソッドで描き変える。

Stageオブジェクト.update()

画像イメージをステージに描くため、以上の処理をAbstractLoader.onFileLoadイベントのハンドラ(draw())に加える。Stageオブジェクトは初期設定の関数(initialize())で変数(stage)に入れておいたので、それを参照している。

var myBitmap;

function draw(eventObject) {
  var myImage = eventObject.result;

  myBitmap = new createjs.Bitmap(myImage);

  stage.addChild(myBitmap);
  stage.update();
}

ここまでご紹介したJavaScriptコードで、画像ファイルを読込んでそのイメージをBitmapインスタンスとしてステージに描くことができる。もっとも、位置決めをしていないので、ステージの左上角にある原点(0, 0)に、Bitmapインスタンスのデフォルトの基準点となる左上角が配置される図1⁠。

図1 ステージの左上角に画像イメージが描かれる
図1 ステージの左上角に画像イメージが描かれる

この後トゥイーンアニメーションするには、Bitmapインスタンスの基準点はイメージの中心に置いた方が扱いやすい。また、アニメーションを始める位置は、Canvasのサイズにもとづいて決めることにする。

Canvasの大きさは、初期設定の関数(initialize())で調べられる。Bitmapオブジェクトの垂直位置を真ん中にするため、Canvasの高さ(heightプロパティ)を変数(bottom)にとった。

Bitmapインスタンスの基準点は、読込んだ画像イメージのサイズをもとに導く。したがって、読み込み終えたとき呼び出されるハンドラ(draw())で処理する。画像イメージ(Imageオブジェクト)の幅と高さ(widthとheight)を調べて、その半分の座標(halfWidthとhalfHeight)をインスタンスの基準点に定める。

基準点のxy座標を決めるプロパティは、DisplayObject.regXDisplayObject.regYだ。インスタンスの幅と高さの半分の値(halfWidthとhalfHeight)を、それぞれに与える。そのうえで、インスタンスの垂直位置DisplayObject.yプロパティ)はCanvasの高さの半分、水平位置DisplayObject.xプロパティ)は左端から基準点の水平座標分内側に置いた。

var bottom;
function initialize() {
  canvasObject = document.getElementById("myCanvas");

  bottom = canvasObject.height;

}
function draw(eventObject) {
  var myImage = eventObject.result;
  var halfWidth = myImage.width / 2;
  var halfHeight = myImage.height / 2;

  myBitmap.regX = halfWidth;
  myBitmap.regY = halfHeight;
  myBitmap.x = halfWidth;
  myBitmap.y = bottom / 2;

}

これで読込まれた画像イメージをもつBitmapオブジェクトが、Canvasの左端で垂直方向は真ん中に描かれる図2⁠。script要素全体は、つぎのコード1のとおりだ。

図2 読込んだ画像ファイルのイメージがCanvas左端に描かれた
図2 読込んだ画像ファイルのイメージがCanvas左端に描かれた
コード1 PreloadJSで読込んだ画像イメージをCanvasに描く
<script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>
<script src="http://code.createjs.com/preloadjs-0.2.0.min.js"></script>
<script>
var stage;
var myBitmap;
var bottom;
function initialize() {
  canvasObject = document.getElementById("myCanvas");
  var file = "images/Pen.png";
  var loader = new createjs.PreloadJS(false);
  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;
  myBitmap = new createjs.Bitmap(myImage);
  myBitmap.regX = halfWidth;
  myBitmap.regY = halfHeight;
  myBitmap.x = halfWidth;
  myBitmap.y = bottom / 2;
  stage.addChild(myBitmap);
  stage.update();
}
</script>

オブジェクトをトゥイーンアニメーションさせる

いよいよTweenJSライブラリを使ったトゥイーンアニメーションだ。左端に置いたインスタンスを、右端にトゥイーンさせる図4⁠。まず、Tweenクラスで、どのようなトゥイーンにするかを決める。Tweenクラスでスクリプトを書くのは、とても直感的だ。Tween.get()メソッドでつくったTweenオブジェクトにドット(.)を加えて、Tweenのメソッドでトゥイーンの設定をいくつでもつなげて呼び出せばよい。Tween.get()メソッドの引数は、トゥイーンするオブジェクトだ。

createjs.Tween.get(オブジェクト).Tweenのメソッド(設定).Tweenのメソッド(設定).…

なお、Tween.get()メソッドは、Tweenオブジェクトをつくることなく、クラスTweenに対して直に呼び出す。このようにクラスを参照して呼び出すメソッドは、⁠静的メソッド」と呼ばれる。

図4 インスタンスを左端から右端にトゥイーンさせる
図4 インスタンスを左端から右端にトゥイーンさせる

今回は、オブジェクトを左から右に動かすだけなので、Tweenのメソッドとしてはひとつだけ、Tween.to()を使う。メソッドの第1引数にオブジェクトのプロパティ値を定めると、オブジェクトのプロパティが今の状態からその値にトゥイーンされる。第2引数はトゥイーンにかけるミリ秒、第3引数にはイージング関数をEaseクラスの静的メソッドで指定する。

createjs.Tween.get(オブジェクト).to(プロパティ指定のオブジェクト, 時間, イージング関数)

Tween.to()メソッドの第1引数には、オブジェクトにプロパティとその値を納めて渡す。一般には、Objectインスタンスの記述({})に、⁠プロパティ:値」の組をカンマ(,)区切りで加えればよい。

{プロパティ:値, プロパティ:値, …, プロパティ:値}

ただ、今回トゥイーンするのはxy座標なので、初めからxy座標がプロパティとして備わっているPointクラスを用いる。

new createjs.Point(x座標, y座標)

Tween.to()メソッドの第3引数のイージング関数は、時間経過に対する値の変わり方を定め、Easeクラスの静的メソッドから選ぶ。バウンドするような変化がおもしろいので、Ease.bounceOut()にしよう。

トゥイーンするプロパティのx座標値(right)は、初めの位置と水平にCanvasの反対端(右端からインスタンス幅の半分内側⁠⁠、y座標はもとのままだ。つまり、水平に左端から右端へアニメーションさせる。加えるスクリプトは、つぎのようになる。なお、Tween.get()メソッドの後ドット(.)を打つ前に改行したのは、Tweenのメソッドをさらにつなげても見やすいようにだ。

var right;
function initialize() {
  canvasObject = document.getElementById("myCanvas");

  right = canvasObject.width;

}
function draw(eventObject) {
  var myImage = eventObject.result;
  var halfWidth = myImage.width / 2;

  right -= halfWidth;

  setTween(myBitmap, new createjs.Point(right, bottom / 2), 3000, createjs.Ease.bounceOut);

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

もっとも、ここまでのスクリプトを書き加えただけでは、オブジェクトのアニメーション起こらない。実は、初めのほうで述べたつぎの一文が伏線になっている。

EaselJSはアニメーションのため一定の時間間隔で繰返し処理する仕組みをもつものの、おそらく無駄な負荷を省くため、画面はStage.update()メソッドで描き変える。

TweenJSライブラリがいくらオブジェクトのプロパティを変更しても、その結果を画面に描かなければアニメーションとして見ることはできない。足りないのは、Stage.update()メソッドを繰り返し呼び出す処理だ。それを関数として定め、⁠一定の時間間隔で繰返し処理する仕組み」に加える。それが、Tickerクラスの静的メソッドTicker.addListener()である。

createjs.Ticker.addListener(オブジェクト)

呼び出す関数はtick()という決まった名前で定める。今回は、ただStage.update()メソッドを呼び出せばよい。Ticker.addListener()メソッドの引数に渡すのは、tick()関数を定めたオブジェクトだ。HTMLドキュメントのscript要素でJavaScriptコードを書いている場所はWindowオブジェクト(windowプロパティ)になる。

function draw(eventObject) {

  createjs.Ticker.addListener(window);
}

function tick() {
  stage.update();
}

これでステージ左端に置かれたインスタンスが、水平に右端までバウンドするようにアニメーションする。書き上げたscript要素全体は、つぎのコード2のとおりだ。jsdo.itにも同じコードを掲げた。

コード2 TweenJSでオブジェクトの座標をトゥイーンさせる
<script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>
<script src="http://code.createjs.com/preloadjs-0.2.0.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.3.0.min.js"></script>
<script>
var stage;
var myBitmap;
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;
  right -= halfWidth;
  myBitmap = new createjs.Bitmap(myImage);
  myBitmap.regX = halfWidth;
  myBitmap.regY = halfHeight;
  myBitmap.x = halfWidth;
  myBitmap.y = bottom / 2;
  stage.addChild(myBitmap);
  stage.update();
  setTween(myBitmap, new createjs.Point(right, bottom / 2), 3000, createjs.Ease.bounceOut);
  createjs.Ticker.addListener(window);
}
function setTween(target, myPoint, time, easing) {
  createjs.Tween.get(target)
  .to({x:myPoint.x, y:myPoint.y}, time, easing);
}
function tick() {
  stage.update();
}
</script>

Easeクラスのイージングを定めるメソッドには、どのように変化するものがあるか。興味をもたれた読者も多いだろう。TweenJSのページに、各メソッドのイージングをグラフで見せてくれるデモSpark Tableがある図5⁠。このデモの右の欄に「Ease Equations」として掲げられたメソッドは、上記コード2で用いたEase.bounceOut()メソッドと差替えれば、イージングが変わる。興味があったら、試してほしい。

図5 Easeクラスのイージングメソッドについて値の変わり方を比べる
図5 Easeクラスのイージングメソッドについて値の変わり方を比べる

これで、お題の基礎は学んだ。次回は、トゥイーンの設定をランダムに変えながら繰返すアニメーションに進もう。

おすすめ記事

記事・ニュース一覧