今から始めるCreateJSで実装するHTMLリッチコンテンツ

エンジニアのたかです。半年ほど前からTokyo Otaku Mode(以下 TOM)にjoinして働いています。
はじめてのブログ投稿は、CreateJSについて書いていきたいと思います。

CreateJSでTOMのロゴマークをParticleにしてみました。
クリックをすることでParticleが弾けるようになっています。はじけたParticleは指定の場所に戻ってくるようにTweenが実行されます。すべてのParticleが指定の場所に戻ると再度、細かいアメーバっぽい動きを始めます。

※ブラウザの環境によってはご覧になれない場合があります。ご了承ください。

■ CreateJSについて

CreateJS | A suite of JavaScript libraries and tools designed for working with HTML5

CreateJSはHTML5のモジュールライブラリの集まりです。
その、CreateJSを使ってcanvasアニメーションを触ってみようと思います。

まずcanvasは、HTML5から新しく実装された要素であり、JavaScriptを使い丸や四角のオブジェクトや画像、テキストを描画することができます。
グラフや3Dの描画などをFlashを使わずにJavaScriptだけで表現することができます。
JavaScriptなので、PCだけではなくスマートフォン用のWebサイトでもFlashのようなリッチコンテンツを表現することができます。
ですが、canvasで実装するコードは、単調なコードの繰り返しが多く、あまり生産性が高くありません。そこで、canvasのライブラリを使うことで、canvasをもっと気軽に扱えるようになります。
現在のcanvasライブラリだと three.js, Pixi.js, CreateJS と数多くライブラリが出ています。ライブラリにはそれぞれ特徴があり、用途や使いやすさによって使い分けるとよいです。
その中でもAdobeが運用・開発をしている CreateJS を今回は説明をしていきたいと思います。Adobeが開発をしたライブラリなので、ActionScriptに似た書き方になっています。なので、Flashを触っていた方は比較的導入しやすいライブラリです。また、Adobe Illustratorのようなレイヤー構造管理でオブジェクトの描画順を簡単に扱えるようになっています。
CreateJSは オブジェクト描画が出来るEaselJS, Tweenアニメーションを使えるTweenJS, 音声データを扱えるSoundJS, 非同期読み込みができるPreloadJS の4つのモジュルールを集めたライブラリです。それぞれのライブラリは個別でも使用することができるので、必要な物だけを読み込ませて使用することも可能です。

以下それぞれのモジュールの説明です。

オブジェクトの描画ができ、オブジェクトをレイヤー構造管理で実装することができます。
また、EaselJS内で大切なクラスにTickerクラスがあります。Tickerクラスを使うことで、定期的にTickイベントを呼び出すことができ、アニメーションを簡単に実装させることができます。デフォルトでは30fpsで実行されます。また、CPUの負荷が高い時は設定したfpsよりも遅い感覚になることがあるので注意が必要です。

Tween Tween できます!ActionScriptと同じようにオブジェクトに簡単にアニメーションを追加させることができます。
Tweenの良さはEaseクラスにあります!Easeの種類は公式のサンプルを確認すると分かりやすいです。サンプルにもありますが固定値で使えるbackInOutもあれば、細かい値を設定できるgetBackInがあり色々と使いまわせることができます。
また、TweenJS内にもTickerクラスが実装されています。

音の再生が簡単に行えます。また、1つの音だけではなく、複数の音の使い分けできることが特徴です。次に説明するPreloadJSと組み合わせることで大きな音源ファイルも効率よく取得することができます。
また、createJSではないが DynamicsCompressorNode - Web API インターフェイス | MDN を使うことで簡単にイコライザーなどを作ることができます。

画像の読み込みや音楽ファイル、テキストファイルなどを非同期で読み込むことができます。また、読み込みの状況などを取得することができるので % 表示とかで読込状況を表現する実装をすることができます。


■ Sample Codes

Demo1

ボールを動かすサンプルを作成します。

sample

index.html

1
2
3
<script src="https://code.createjs.com/createjs-2014.12.12.min.js"></script>
<script src="./canvas.js"></script>
<canvas id="canvasElement" width="300" height="300"></canvas>

※ canvas要素はstyle属性のwidthheightを指定しても表示はされないです。img要素のようにcanvas要素の属性でwidthheightを設定します。

canvas.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
window.onload = function() {
var stage = new createjs.Stage("canvasElement");
var circle = new createjs.Shape();
circle.graphics.beginFill("red").drawCircle(0, 0, 20);
circle.y = 140;
stage.addChild(circle);
createjs.Ticker.addEventListener("tick", function(){
circle.x += 5;
if(300 < circle.x){
circle.x = 0;
}
stage.update();
});
};

2行目でStageクラスにcanvas要素のidを渡してインスタンス化しています。これでcanvasを使う準備が整います。その後は必要な球体やテキスト、画像をオブジェクト化してstageクラスにaddChildすると画面に表示がされます。
Tickerイベントでメインループを回して、球体をアニメーションさせます。Tickerは指定がない場合はframerateが、20で設定されています。20は1000msの間に20回実行されることを指します。なのでframerateが20だと1000/20 = 50ms に一回実行されることになります。stage.update()で画面を更新させるので、Tickerで実行させて毎回再描画させます。これによりボールを左から右に動くアニメーションを作ることができます。

Demo2

CreateJSのライブラリのTweenを使うと動きの幅を色々と持たせることができます。
tweenの記述は以下のようになります。

sample

canvas.js
サンプル1で作成したjsをすこし改良して以下のようにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
window.onload = function() {
var stage = new createjs.Stage("canvasElement");
var circle = new createjs.Shape();
circle.graphics.beginFill("red").drawCircle(0, 0, 20);
circle.y = 140;
stage.addChild(circle);
createjs.Tween.get(circle, {loop: true})
.to({x: 300}, 2000, createjs.Ease.elasticInOut)
.call(function(){
circle.y = Math.random() * 300;
});
createjs.Ticker.addEventListener("tick", function(){
stage.update();
});
};

サンプル1ではボールを左から右に5pxずつ動かしてアニメーションをさせましたが、今回はアニメーションの箇所をTweenで実装をさせて動きに変化を付けさせます。

sample Tween code

1
2
3
createjs.Tween.get(target)
.to({x: 200}, 100, createjs.Ease.cubicInOut)
.call(function(){ console.log("finish tween"); });

  • getで実行させたいターゲットを指定します。また、optionとかも指定することができます。詳しくはTweenJS v0.6.1 API Documentation : Tweenをご確認ください。
  • toでアニメーションの実行の情報を記述します。第1引数に変化する形状の情報、第2引数にアニメーションの実行時間、第3引数にアニメーションのイージングを指定できます。
  • callでTweenアニメーションが完了した時に実行されます。

まだ、waitset などの関数があります。詳しくはTweenJS v0.6.1 API Documentation : Tweenをご覧ください。

以上のように、コードをあまり書かなくてもオブジェクトの表示やTweenを実装することができます。今回はSoundJSとPreloadJSのサンプルはありませんが、同じように簡単に実装ができます。


■ TOM Logo Particle サンプルコード について

ページのトップに表示させているTOMのLogoをParticle表示させているCodeをコメント付きでのせておきます。

実装内容を簡単に説明します。初期にTOMのLogoの画像データを読み込ませて、canvas上に表示させます。その後、画像情報として読み取ります。読み取った画像情報から値を縦横7pxごとにピクセルデータのRGBの合計が700以上の箇所に、円を並べていきます。あとは、クリック処理とTween処理を実装しました。弾ける処理の箇所では、クリックした位置から遠いほど弾ける力が弱くなるように設定をしました。

また、今回のロゴではParticleの数は約2500です。これ以上Particleを増やすとカクつきが目立ってしまい綺麗なTweenになりませんでした。

canvas.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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
(function(){
var mView = null,
stage = null,
particleStage = null,
particles = [],
baseParticles = [],
stageNamePattern = {
START: 0,
SHOW: 1,
MOVE: 2
},
stageName = stageNamePattern.START,
imgFile = null,
mousePoint = new createjs.Point(),
MOUSE_BOUNDS_RADIUS = 200,
isBound = false,
isClick = false,
// 2点から直角三角形3辺の長さを求める
calcuSides = function(basePoint, targetPoint) {
var _x, _y, _z;
_x = Math.abs(basePoint.x - targetPoint.x);
_y = Math.abs(basePoint.y - targetPoint.y);
_z = Math.sqrt(Math.pow(_x, 2) + Math.pow(_y, 2));
return { x: _x, y: _y, z: _z };
},
// 2辺の長さからsinθを求める
calcu2PointSin = function(basePoint, targetPoint) {
var sides, sin;
sides = calcuSides(basePoint, targetPoint);
sin = sides.y / sides.z;
if (basePoint.y > targetPoint.y) {
sin *= -1;
}
return sin;
},
// 2辺の長さからcosθを求める
calcu2PointCos = function(basePoint, targetPoint) {
var cos, sides;
sides = calcuSides(basePoint, targetPoint);
cos = sides.x / sides.z;
if (basePoint.x > targetPoint.x) {
cos *= -1;
}
return cos;
},
// 画像情報からRGBの合計値が700以上の箇所のピクセルデータを取得する
createLocationData = function(imgMap) {
var buket, height, imgData, index, results, width, x, y;
imgData = imgMap.data;
width = imgFile.width;
height = imgFile.height;
y = 0;
results = [];
while (y < height) {
x = 0;
while (x < width) {
// 配列の格納順が [r, g, b, alpha, r, g, b, alpha...] という順番で格納されているので、4つ分ずらしている。
index = (x + y * width) * 4;
// rgbの合計値が700以下のピクセルだった場合に取得
if (700 > imgData[index + 0] + imgData[index + 1] + imgData[index + 2]) {
buket = {
x: x + (mView.width() / 2) - (imgFile.width / 2),
y: y + (mView.height() / 2) - (imgFile.height / 2),
baseX: x,
baseY: y,
r: imgData[index + 0],
g: imgData[index + 1],
b: imgData[index + 2],
alpha: imgData[index + 3],
tweenX: false,
tweenY: false,
scale: Math.random() * 1 + .5,
bound: false
};
baseParticles.push(buket);
}
x += 7;
}
results.push(y += 7);
}
return results;
},
// ポコポコと泡っぽくパーテクルを表示させるときのTween
showParticles = function() {
var circle, i, index, len, particleCount, results, scale;
if (stageName === stageNamePattern.START) {
stageName = stageNamePattern.SHOW;
particleCount = 0;
results = [];
for (index = i = 0, len = particles.length; i < len; index = ++i) {
circle = particles[index];
circle.scaleX = 0;
circle.scaleY = 0;
scale = baseParticles[index].scale;
results.push(createjs.Tween.get(circle).wait(Math.random() * 500 + 500).to({
scaleX: scale,
scaleY: scale
}, Math.random() * 2000 + 1000, createjs.Ease.getElasticOut(Math.random() * 5 + 1, Math.random() * .25 + .1)).call(function() {
particleCount++;
if (particleCount === particles.length) {
return setTimeout(function() {
return stageName = stageNamePattern.MOVE;
}, 100);
}
}));
}
return results;
}
},
// 画像情報から作成したParticleのデータから1つ1つ丸のオブジェクトを生成する
createLogo = function() {
var circle, i, index, len, local, results, rgba;
results = [];
for (index = i = 0, len = baseParticles.length; i < len; index = ++i) {
local = baseParticles[index];
circle = new createjs.Shape();
circle.no = index;
circle.x = local.x;
circle.y = local.y;
circle.scaleX = 0;
circle.scaleY = 0;
rgba = "rgba(" + local.r + "," + local.g + "," + local.b + ", 1)";
circle.graphics.beginFill(rgba).drawCircle(0, 0, 2);
particles.push(circle);
results.push(particleStage.addChild(circle));
}
return results;
},
// バウンドのTween と クリック操作がない場合のアメーバTween
moveParticles = function() {
var _x, _y, i, index, len, particle, rate, results, sides, sin, velocityX, velocityY;
results = [];
for (index = i = 0, len = particles.length; i < len; index = ++i) {
particle = particles[index];
_x = particle.x;
_y = particle.y;
// バウンドのTween
if (isClick) {
particle.bound = true;
particle.tweenX = false;
particle.tweenY = false;
sides = calcuSides(mousePoint, { x: _x, y: _y });
if (sides.z < MOUSE_BOUNDS_RADIUS) {
rate = {
x: MOUSE_BOUNDS_RADIUS * calcu2PointCos(mousePoint, { x: _x, y: _y }),
y: MOUSE_BOUNDS_RADIUS * calcu2PointSin(mousePoint, { x: _x, y: _y })
};
sin = calcu2PointCos(mousePoint, { x: _x, y: _y });
velocityX = rate.x + Math.random() * 20 + 10;
velocityY = rate.y + Math.random() * 20 + 10;
createjs.Tween.get(particle, { override: true }).to({
x: particle.x + velocityX,
y: particle.y + velocityY
}, (Math.random() * 1200 + 1000) * (1 - sides.z / MOUSE_BOUNDS_RADIUS), createjs.Ease.backOut).call(function() {
return createjs.Tween.get(this).to({
x: baseParticles[this.no].x,
y: baseParticles[this.no].y
}, Math.random() * 5000 + 4000, createjs.Ease.getElasticOut(1, Math.random() * .25 + .1));
});
}
// アメーバTween
} else if (!isBound) {
if (!particle.tweenX) {
particle.tweenX = true;
createjs.Tween.get(particle).to({
x: baseParticles[particle.no].x + Math.random() * 10 - 5
}, Math.random() * 900 + 700, createjs.Ease.sineInOut).call(function() {
return this.tweenX = false;
});
}
if (!particle.tweenY) {
particle.tweenY = true;
results.push(createjs.Tween.get(particle).to({
y: baseParticles[particle.no].y + Math.random() * 10 - 5
}, Math.random() * 900 + 700, createjs.Ease.sineInOut).call(function() {
return this.tweenY = false;
}));
}
}
}
if(isBound) {
//tweenで動いているオブジェクトがないかを確認
var isActive = false;
particles.forEach(function (particle) {
if (isActive) {
return
}
isActive = createjs.Tween.hasActiveTweens(particle);
});
if (!isActive) {
isBound = false;
}
}
},
// Tickerで呼び出されるメインループ
mainLoop = function() {
switch (stageName) {
case stageNamePattern.START:
showParticles();
break;
case stageNamePattern.MOVE:
moveParticles();
}
stage.update();
return isClick = false;
};
// 実行関数
// PreloadJSで画像を読み込未処理で使用する。
// 読み込み完了後にmainloopを起動する。
window.onload = function() {
var canvas, context, manifest, queue, canvasEle;
stage = new createjs.Stage("canvas");
particleStage = new createjs.Container();
mView = $("body");
canvasEle = $("#canvas");
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
stage.on("stagemouseup", function(evt) {
isClick = true;
isBound = true;
mousePoint.x = evt.stageX;
return mousePoint.y = evt.stageY;
});
manifest = [{ id: "logo", src: "assets/logo.jpg" }];
queue = new createjs.LoadQueue();
queue.on("complete", function() {
var logoImg, logoImgMamp;
imgFile = queue.getResult("logo");
// canvasElementのwidthとheightを画面サイズに変更させる
canvasEle.prop("width", mView.width());
canvasEle.prop("height", mView.height());
// 一度画像をstage上に表示する
logoImg = new createjs.Bitmap(imgFile);
stage.addChild(logoImg);
stage.update();
// 表示させているオブジェクトをimageMapとして取得する
// 取得後はstageから画像オブジェクトをremoveする
logoImgMamp = context.getImageData(0, 0, imgFile.width, imgFile.height);
createLocationData(logoImgMamp);
stage.removeAllChildren();
// 取得した値からParticleオブジェクト生成してstageに追加する
createLogo();
stage.update();
stage.addChild(particleStage);
// 33fpsでTickerを実行する
createjs.Ticker.framerate = 33;
return createjs.Ticker.addEventListener("tick", mainLoop);
}, this);
return queue.loadManifest(manifest, true);
};
})();


■ まとめ

最後の方は駆け足になりましたが、CreateJSを使用して簡単なアニメーションを実装してみました。
また、今回は説明できませんでしたが、CreateJSにはアニメーションだけでなく、音楽再生、非同期処理に関するモジュールも含まれています。英語ですがドキュメントも充実しており、canvasを使用したリッチコンテンツを作成するために必要な環境が一通り揃っています。
さらに、CreateJSはPure JavaScriptで記述されていますので、こちらの記事(CreateJSとNode.jsを使ってサーバーサイドでCanvasを扱おう–ICS LAB)に説明されているように、Node.jsとCreateJSを使用してサーバサイドレンダリングすることもできます。
ActionScriptの代替品としても使用できる非常に強力なプログラミングツールですので、ご興味のある方は是非一度お試しください。

Tokyo Otaku Mode からのお知らせ

Tokyo Otaku Mode では現在一緒に働く仲間を募集中です!
TOMで働くことに興味がある方はこちらからご応募ください。