Flashのように滑らかなアニメーションを実装するには(uupaa.js vs jQueryデモ)
JavaScript で Flash のような滑らかなアニメーションを行うためには、クロスブラウザな知識の他に、GC(ガベージコレクション)や「どうすれば安定した品質がだせるのか」といったスキルが求められます。
# GC の話は WEB+DB PRESS 57 でちょっと書いてます。
派手目なアニメーションが目的で jQuery を採用している方もいるとは思いますが、実は uupaa.js でも アニメーションや easing が利用できるんです。あらびっくり。
ただ「機能がありますよ〜」だと、さみしいので
みんな大好き jQuery と uupaa.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 をご覧ください。