Flashのように滑らかなアニメーションを実装するには(uupaa.js vs jQueryデモ)


JavaScriptFlash のような滑らかなアニメーションを行うためには、クロスブラウザな知識の他に、GC(ガベージコレクション)や「どうすれば安定した品質がだせるのか」といったスキルが求められます。
# GC の話は WEB+DB PRESS 57 でちょっと書いてます。

派手目なアニメーションが目的で jQuery を採用している方もいるとは思いますが、実は uupaa.js でも アニメーションや easing が利用できるんです。あらびっくり。

ただ「機能がありますよ〜」だと、さみしいので

みんな大好き jQueryuupaa.js のアニメーションの品質の違いが分かるようなデモを作成してみました。
iPhone/iPad や、IE6〜IE8で opacity を切り替えて見ると、ハッキリと違いが分かると思います。

http://jsdo.it/uupaa/uufxVsjQueryAnimate/fullscreen

jQuery に無い機能として、uupaa.js には 最適化されたカラーアニメーションや、ある期間からある期間までを指定してリバースするリバースアニメーションの機能などもあります。

どんなことやってるか

長くなるのでポイントだけ。

http://code.google.com/p/uupaa-js/source/browse/trunk/0.8/src/uupaa.js?r=809#1760

// uu.interval - interval timer
function uuinterval(callback, // @param Function: callback
                    arg) {    // @param Mix: arg
                              // @return Number: id
    var id = uuguid();

    uuinterval.ary.push(id, callback, arg);
    if (uuinterval.base === null) {
        uuinterval.base = setInterval(tick, _ver.chrome ? 4 : 12);
    }
    return id;
}
uuinterval.ary = [];    // [<id, callback, arg>, ...]
uuinterval.base = null; // base interval timer id

// inner - tick interval timer
function tick() {
    var ary = uuinterval.ary, i = 0, iz = ary.length;

    for (; i < iz; i += 3) {
        // callback(timer.id, arg) -> false is loopout
        if (ary[i + 1](ary[i], ary[i + 2]) === _false) {
            ary.splice(i, 3);
            i  -= 3;
            iz -= 3;
        }
    }
}

# uuinterval.ary と uuinterval.base は LookDown です。WEB+DB PRESS 57 でちょっと書いてます。

  • アニメーションキューは一つの setInterval で回している。Chromeなら4ms, 他のブラウザでは12ms間隔
  • 内部関数 tick が定期的に呼ばれる
  • データ構造が { id: [callback, arg], ... } な Hash ではなく、フラットな配列(uuinterval.ary)で組んでいるのは、for in ループが、for ループよりも高コストなため(特にIEで)
  • callback が false を返すと、Array.splice() で該当する情報だけ削ってる。
    • Array.splice() はインデックスの張替が発生するため高コスト。
    • 本来であれば、フラグを立ててループの外で uuinterval.ary を再構築すべきだが、大量の要素を扱うケースもありうるので、ループをもう一度まわすコスト + 配列を再生成するコスト ≧ Array.splice() と判断。
      • MobileWebKitモードではコード量の制限もあるため、ダラダラとコードが増やせず最小手でロジックを書く必要があることからも、splice()でやってる

タイマー周りのバックグラウンドはこんな感じです。

追記: Chromeなら4ms, 他のブラウザでは12ms間隔 → これは記事を書いた当時の値ですね。
HTML5を取り込んだ Firefox5, IE10pp2 では setTimeout が最小 4ms, setInterval が最小 10ms (だったかな?) で動作します。また、そもそも window.requestAnimationFrame の利用が推奨されており、setInterval は requestAnimationFrame が利用できない場合に仕方なく使う形になります。requestAnimationFrame は、既に IE10pp2, Firefox4+, Chrome で利用可能です。

タイマーから呼び出される関数も、かなり頑張ってる

アニメーションの品質を確保するためにはタイマー周りだけ頑張ってもだめで、タイマーから定期的に呼ばれる関数でも、かなり頑張ってます。

タイマーから定期的に呼ばれる関数は、uufxbuild でアニメーション開始時に動的に生成しています。

ポイントは、

  • コストの最小化
    • new Function() で文字列から関数を構築
    • 関数呼び出しを限界まで排除。easing 関数もインライン化
    • 与えられた引数だけを参照し、外部スコープを参照しない

などです。詳しくはソースをご覧ください(軽い黒魔法ですが)。

iPhoneなどリソースが限定されている環境だと、jsdo.it 上のサンプルは、コード本来の性能が出せないようなので、実際の動きは http://code.google.com/p/uupaa-js/wiki/uu_fx#Test_Code をご覧ください。