Sencha Animatorで作るCSSアニメーションがすごく良い感じ

最近Android, iOS上のWebブラウザでのアニメーションについてちょっと調べています。

有名所だと、こんな感じのがあります。

  • CreateJS
  • LWF
  • AdobeEdge

ただ、どれもJSをゴリゴリ使ってタイムラインの制御と各フレームでのオブジェクトの状態を計算しています。 というわけでモバイル上ではやっぱりパフォーマンスがきついです。特にDOMがたくさんある画面に複数のアニメーションを配置して動かすとかくかくです。(iPhone5はヌルヌル動きますけど)

で、試しに同じような動きをCSS3 Animatonで作って見たところ、iPhone4でもiPhone5並にヌルヌル動きます。Android4ではヌルヌルとまではいかないですが、結構いい感じに動きます。(Android2.3は検証から外してます)

やっぱりCSS3かー。となったわけですが、CSS3でアニメーションを作っていくのはすごく大変。職人技が必要。。。

で、そんな時にSencha Animatorを見つけました!これがすごい!Flashに似た感じのタイムラインベースのオーサリングツールを使ってアニメーションを作成します。アニメーション自体はCSS3 Animationを使い、JSはアニメーションのイベント処理を行うために補助的に使う感じです。

ツール レンダリング タイムライン フレーム オーサリングツール
Sencha Animator DOM CSS3 Animation CSS3 Animation あり
CreateJS Canvas JavaScript JavaScript なし (Flashから変換)
LWF Canvas, DOM JavaScript JavaScript なし (Flashから変換)
Adobe Edge DOM JavaScript JavaScript あり

※タイムライン - JavaScript : setTimeout/requesetAnimationFrameを使って制御

※フレーム - JavaScript : 各フレームでのオブジェクトの形や位置をJavaScriptで計算してオブジェクトを更新する

Sencha Animator

まずはデモをみてもらうのがよいと思います。 http://animator.sencha.com/demo/HauntedMansion/

これがCSS3 Animationだけで出来てるとはすごすぎる。もちろんクリック処理にJSが使われていますが、あくまで補助的ですね。しかもJSを書く必要はなくて、Sencha Animator上からクリック処理やマウスオーバー処理をポチポチ選ぶだけです。

どんなことができるのか?

基本機能

  • CSS3のみでアニメーション
  • アニメーション内には複数のシーンを構成してシーンを切り替えることができる
  • シーン内には複数のタイムラインを構成してタイムラインを切り替えることができる
  • タイムライン内には複数のオブジェクト(画像やテキスト)を配置してそれぞれ独立したキーフレームを打つことができる
  • 複数のオブジェクトをグループ化して単一のオブジェクトとしてアニメーションさせることができる
  • オブジェクトをシンボル化することで再利用できる
  • ブラウザで即座にプレビューする機能がある
    • もちろんSencha Animator上でも即座にプレビューできる

イベント処理

  • 要素をクリックした時などのイベント処理を設定できる
  • シーン、タイムラインの開始や終了などのイベント処理を設定できる
  • イベント処理は定義済みのもの(シーン移動やタイムライン移動など)から選択するか、JSを書くこともできる

JS連携

  • アニメーションをHTML内の任意の要素に動的に埋め込んで再生することができる
    • この場合はXHRを使ってアニメーション定義ファイル(data.json)を読み込んでくる
    • data.jsonと同じ位置にassetsも必要
    • data.jsonにはhtml, css, js, controllerjsが含まれる
      • html: アニメーションを行うためのDOM構造
      • css: アニメーションのCSS定義
      • js: アニメーションのシーンやタイムラインのデータ
      • controllerjs: アニメーションの制御処理
  • 複数のアニメーションを1つのHTMLに埋め込んで再生できる
  • 動的にイベント処理を設定することができる
  • シーンの移動やタイムラインの移動などを制御できる
    • ただし任意の位置でアニメーションを停止することはできなさそう
  • 動的にアニメーション内のテキストや画像を差し替えられる
    • Sencha Animator上でid, classをつけて、JS側からDOM要素のプロパティを変更する
    • ただし要素を書き換えることができるタイミングはアニメーション開始後なので、少し工夫が必要(これは後述する)

その他

  • 標準はWebKitのみ。Firefoxにも一応対応してる
  • iOS, Android対応あり(実機検証必要
  • android2.3モードあり(限られたプロパティのみ使える

JS連携デモ

JSと連携させたデモです。

やっていることは

  • 動的にアニメーションを読み込んで表示
  • 1つのアニメーション内の画像を差し替えて異なるアニメーションとして表示
  • クリック時のシーンの移動を制御
  • シーン終了時のイベント処理を動的に設定
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sencha Animator Sample Embed</title>
  <script type="text/javascript" src="senchaAnimatorEmbed.js"></script>
</head>
<body>

<div>Click Octocat</div>
<div id='manual-animation1' data-octocat="original_128x128.png" style="display: inline-block"></div>
<div id='manual-animation2' data-octocat="class-act_128x128.png" style="display: inline-block;"></div>
<div id='manual-animation3' data-octocat="octobiwan_128x128.jpg" style="display: inline-block;"></div>
<div id='manual-animation4' data-octocat="puppeteer_128x128.png" style="display: inline-block;"></div>
<div id="log"></div>

<script type="text/javascript">

  // アニメーションを埋め込む要素のID
  var ids = ['manual-animation1', 'manual-animation2', 'manual-animation3', 'manual-animation4'];

  for (var i = 0; i < ids.length; i++) {
    /*
     * 指定した要素にアニメーションを埋め込む.
     * 第2引数はアニメーションデータ(data.json, assets)が存在するパス.
     */
    AN.PageManager.loadAnimation(ids[i], "./", {
      /*
       * アニメーションが読み込み完了時の処理.
       *
       * この時点では既にアニメーションの再生が始まっている.
       * アニメーション開始前に処理をする場合はonLoadの代わりにonBeforeRunを使用する.
       * ただしonBeforeRunではまだControllerの初期化が終わってないのでできることが少ない.
       *
       * そこでアニメーション作成時にScene0に何も要素をもたない空のシーンを入れている.
       * onLoad内での初期化処理が終わってから実際に再生させたいシーンに移動させている.
       */
      onLoad: function (instance) {
        // コントローラはアニメーションの動作制御を行う
        var controller = instance.getController();

        // アニメーションが埋め込まれている要素を取得するgetterがなかったのでconfigから取得している.
        var rootElement = instance.config.element;

        /*
         * アニメーション内の画像を差し替えて、同じアニメーションを再利用している.
         * この差し替え機能を使えばアバターの実装が比較的容易に行える.
         */
        var imgName = rootElement.getAttribute('data-octocat');
        var imgUrl = controller.getUrlForLocalAsset(imgName);
        // Sencha Animator上でオブジェクトにつけたIDを取得するにはコントローラのgetElementByIdを使用する.
        // document.getElementByIdでは取得できない.
        // Document上にはアニメーション固有のプレフィックスが付加された状態でIDが存在しているため.
        controller.getElementById('hover-octocat').querySelector('img').src = imgUrl;
        controller.getElementById('rotate-octocat').querySelector('img').src = imgUrl;

        // アニメーションの要素がクリックされた時の処理(通常のDOMイベント)
        controller.getElementById('hover-octocat').onclick = function () {
          controller.goToSceneByName('rotate');
        };

        // シーンが終了した時のActionを追加する.
        // Sencha Animator上でCustomJavaScriptとして追加できるActionは動的に追加できる
        // 全てのActionを変更できるかは未確認.
        var rotate = controller.getSceneByName('rotate');
        rotate.exitAction = function () {
          document.getElementById('log').innerHTML += 'exit ' + imgName + '<br/>';
        };

        //初期化処理が終わったので空シーンから移動してアニメーションを開始する.
        controller.goToSceneByName('hover');
      }
    });
  }
</script>
</body>
</html>

という感じでかなり完成度の高いものだと思います。レンダリングもDOMを使っているのでCanvasのものよりかはるかにJSから操作しやすくなっています。スクリプト自体もそんなに複雑ではないですし。

まだiPhone4や低スペックAndroid4での検証は行ってないので、そちらは随時やっていきます。