みなさんこんにちは!
現在ブラウザベースの新規ゲームの立ち上げをしています、2013年入社のチャーリー@charlie10151015です!
主にフロント側をメインにJavaScriptというへんてこな言語を書いています。
最近のマイブームは3D関連でthreejs, webGL, Blenderなどに手を伸ばしております。

さて、本日は社内で利用されているJavaScriptライブラリのひとつである tofu.jsについてお話できればと思います。

tofu.jsとは?

tofu.jsはHTML5に組み込まれたcanvasをFlashライクなコードで利用できるJavaScriptのライブラリです。
弊社の主席エンジニアが開発し、公開もされているので是非使ってみてください!

- github https://github.com/suguru/tofu.js

tofu.jsの主な特徴として
描画範囲の限定
DOMに書き出すHTML_MODE
の2つが挙げられます。

簡単な使い方とともに、以上の2点を説明していきたいと思います。

描画範囲の限定

canvasを利用する際にcanvas全体をフレームごとに再描画することがあります。
毎フレーム画面全体を大きくアニメーションするような場合、canvas全体を再描画することは有効かもしれませんが、画面の一部のみをアニメーションする場合に画面全体を再描画すると再描画する必要なない静止した部分の無駄なレンダリングが発生してしまいます。

そこでtofu.jsでは新しい状態の画面全体ではなく、現在のフレームと次のフレームの画面の差分のみをレンダリングすることで描画にかかる負荷を下げています。描画範囲の限定を内部的にしており、tofu.jsを利用する開発者が意識せずに描画範囲の最適化を行ってくれます。

tofu.jsにはdebug modeとして再描画領域を可視化してくれるオプション(#1)があるので有効にして実際にアニメーションしてみます。

#1
// 再描画範囲の表示 (tofu.js line: 26)
STROKE_DRAW_REGION
= 1;
実際に書いたコードは以下です。
#1

var w = 400,
h = 400,
frameRate = 20,
imageArray = [];

// create stage
var stage = tofu.createStage({
width: w,
height: h,
frameRate: 20
});

// add renderer elm
document.getElementById('stage').appendChild(stage.container());

// create field
var field = tofu.createGraphics({
width: w,
height: h
});
field.fillStyle = '#EEE';
field.fillRect(0, 0, field.width, field.height);

// add field to stage
stage.add(field);

// create image
for (var i = 0 ; i < 3; i++) {
var image = createImage();
imageArray.push(image);
}

// start animation
animation();

function createImage() {
// create image
var image = tofu.createBitmap({
url: 'http://jsrun.it/assets/9/P/Y/C/9PYCC.png',
scaleX: 0.5,
scaleY: 0.5
});
image.on('load', function() {
image.dx = 2 * Math.random();
image.dy = 2 * Math.random();
image.x = (w - image.width / 2) * Math.random();
image.y = (h - image.width / 2) * Math.random();
field.add(image);
});
return image;
}

// animaion
function animation() {
for (var i = 0 ; i < imageArray.length; i++) {
var image = imageArray[i];
if (!image.x || !image.y) continue;
image.x += image.dx;
image.y += image.dy;
if (image.x > w - image.width / 2 || image.x <= 0) {
image.dx *= -1;
}
if (image.y > h - image.height / 2 || image.y <= 0) {
image.dy *= -1;
}
image.update();
}
requestAnimationFrame(animation);
}

#2 再描画範囲表示サンプル (jsdoit) ※リンクで開いてください
$1 pixel|サイバーエージェント公式クリエイターズブログ-redraw region

サンプル(#2)を見ると赤い枠で囲まれている範囲が基本的な再描画範囲です。
オブジェクトが重なった部分は重なったオブジェクト全てを含めた短形でレンダリングすることによりレンダリングの回数を減らしています。
オブジェクトが移動するとそれに伴って再描画範囲も動的に変化し、最適化されていることが分かると思います。

DOMで書き出すHTML_MODE

次にtofu.jsのHTMLモードを紹介します。

スマートフォンでcanvasを使うにあたり、ひとつの壁となるものが様々な機種によってcanvasのパフォーマンスが異なるということです。
iOS, AndroidなどのOSの違い、safari, chromeなどのブラウザの違い、また機種ごとによってcanvasのパフォーマンスは様々です。特にAndroid4.x系では、canvasの一部の処理が非常に重いという問題があります。

tofu.jsでは、canvasで描画するモードに加えて、DOMを使い画面を制御するHTMLモードを用意し、OSに合わせて切り替える仕組みで対処しています。
#4のようにたった一行でHTMLモードに切り替わり、canvasで処理していたものと同じソースでHTMLを吐き出してくれます。

実際に画像を一枚表示するサンプルを書いてみます。(#5)

#5

if (htmlモードにするOSなど) {
tofu.htmlMode();
}

// stageの作成
var stage = tofu.createStage({
width: 400,
height: 400,
frameRate: 40
});

// bodyに描画領域をappend
document.body.appendChild(stage.container());

// bitmap imageの生成
var image = tofu.createBitmap({
url: 'simon.png'
});

// loadされたら描画
image.on('load', function() {
image.update();
});

// stageにimageを追加
stage.add(image);

$1 pixel|サイバーエージェント公式クリエイターズブログ
以上のコードで画像が表示できました。
次にcanvasモード, htmlモード時のbodyの中身を見てみます。

#6 canvasモードで生成されたbodyの中身
<body>   
<canvas width="400" height="400" style="width: 400px; height: 400px;">
</canvas>
</body>

canvas一枚のみがbodyに挿入されます。
次にHTMLモードの時を見てみましょう。

#7 htmlモードで生成されたbodyの中身
<body>
<div class="tofu tofu tofu-stage" style="width: 400px; height: 400px; overflow: hidden;">
<div class="tofu tofu-stage-body">
<div id="tofu1" class="tofu tofu-object" style="-webkit-transform: matrix(1, 0, 0, 1, 0, 0); background-image: -webkit-canvas(c1); width: 200px; height: 200px; background-repeat: no-repeat no-repeat;"></div>
</div>
<div class="tofu tofu-stage-touch" style="width: 400px; height: 400px;">     
</div>
</div>
</body>

このように全てDOMとして挿入されます。
tofu.jsのモードを切り替えるだけで開発者は意識せずにcanvasかDOMでの描画を切り替えることができます。canvasでの描画が著しく重い端末はHTMLモードを試してみると良いかもしれません。

Android ファースト

社長がブログでちらっとAndroidファーストについて触れています。(Androidファーストについては様子見状態ですが...)

渋谷ではたらく社長のアメブロ
http://ameblo.jp/shibuya/entry-11607865241.html

現在のスマートフォンシェアのiOSとAndroidの比率は、Androidの方が多いです。
ブラウザゲームなどを作り込んでいると、どうしても総合的にブラウザの動作が快適なiPhoneベースでものを作ってしまいがちです。
iPhoneで検証しながら作っていたものがAndroidの特定端末で動作しない、なんてことは日常茶飯事だと思います。しかし、現状で多くのシェアを獲得しているAndroidでもしっかりと快適に動くアプリケーションを作っていかなければなりません。

tofu.jsはひとつのソースでcanvasでの描画、DOMでの描画を効率的に切り替えることができ機種ごと、OSごとの特性を見て最適な描画方法を検討可能です。

現在のプロジェクトでは、Android4.x系の最新機種で実装されはじめているwebGLを使い、処理の高速化を試みています。
まだ特定機種でしか実装はされていませんが、スマートフォンブラウザ上でwebGLなどの技術にいち早く挑戦し、すべての端末でさらに快適なアプリケーションを作っていきたいと思います。

最後に

最後までお付き合いいただきありがとうございました。
今回はtofu.jsについて紹介させていただきました。
tofu.jsはMITライセンスで利用できるので是非興味がわいたら使ってみてください。

- tofu.js https://github.com/suguru/tofu.js

次回は3D周りでなにか書かせていただければと思います!
ありがとうございました!