ストレスの無いCSSアニメーションを
今日は、CSSアニメーション機能を担当する uu.tween.js のリライトをしていました。
uu.tween.js を組み込むと、CSS の色, サイズ, 位置を利用したアニメーションが可能になります。これ自体はよくある機能なのですが、他のライブラリにない特徴として、CSSプロパティ個別に easing 関数を割り当てられます。
デモ
http://pigs.sourceforge.jp/blog/20091216/
ノード3個 と 15個版があります。ノード数が違うだけで何身は一緒です。デモにある円弧軌道は、left, top に別々の easing 関数を割り当てて実現しています。
easing 関数
デフォルトの easing関数 は easeInOutQuad です。plug/easing/uup.easing.js を組み込むと他の関数を利用できます。
http://code.google.com/p/uupaa-js/source/browse/trunk/0.7/plug/easing/uup.easing.js
コード
http://code.google.com/p/uupaa-js/source/browse/trunk/0.7/uu.tween.js?r=207
計算量の最小化やループを排除するため、文字列を組み立て動的に関数を生成しています。初見だと複雑に見えると思いますが、やってることは非常にシンプルです。処理時間もたいしたことありません。
以下の _twloop が setInterval タイマーから起��され、CSSプロパティを評価する部分です。
function _twloop() { var q = node.uutweenq[0], tm = q.tm ? +new Date : (q.js = _twjs(node, q.pa), q.tm = +new Date), fin = q.fin || (tm >= q.tm + q.ms); q.js(node, fin, tm - q.tm, q.ms); // call(node, finish, gain, ms) if (fin) { clearInterval(node.uutween); } }
_twloop から呼ばれる q.js は以下のようになっています。
function(node, fin, gain, ms) { var t, b, c, ms2 = ms / 2, ns = node.style; // node.style.left ns.left = (fin ? 400 : Math.easeOutBack(gain, 0, 400, ms)) + "px"; // node.style.top ns.top = (fin ? 400 : (t = gain, b = 30, c = 370, (t /= ms2) < 1 ? c / 2 * t * t + b : -c / 2 * ((--t) * (t - 2) - 1) + b)) + "px"; // node.style.width var v = fin ? 200 : (t = gain, b = 100, c = 100, (t /= ms2) < 1 ? c / 2 * t * t + b : -c / 2 * ((--t) * (t - 2) - 1) + b); v = v < 0 ? 0 : v; ns.width = v + "px"; // node.style.backgroundColor var gms = gain / ms, hex = uu.dmz.HEX2; ns.backgroundColor = "#" + (hex[(fin ? 135 : (135 - 128) * gms + 128) | 0] || 0) + (hex[(fin ? 206 : (206 - 128) * gms + 128) | 0] || 0) + (hex[(fin ? 235 : (235 - 128) * gms + 128) | 0] || 0); // node.style.opacity var _2 = (t = gain, b = 1, c = -0.5, (t /= ms2) < 1 ? c / 2 * t * t + b : -c / 2 * ((--t) * (t - 2) - 1) + b); _2 = (_2 > 0.999) ? 1 : (_2 < 0.001) ? 0 : _2; ns.opacity = fin ? 0.5 : _2; // node.style.fontSize ns.fontSize = (fin ? 36 : (t = gain, b = 16, c = 20, (t /= ms2) < 1 ? c / 2 * t * t + b : -c / 2 * ((--t) * (t - 2) - 1) + b)) + "px"; }
この戦略は、Ext.js の querySelectorAll が内部的にやっていることと大体おんなじです。
関数の中身は sprintf で作っています。
_FMT = ['var t,b,c,ms2=ms/2,ns=node.style;', 'var _2=%3$s;_2=(_2>0.999)?1:(_2<0.001)?0:_2;' + // opacity (uu.ie ? 'ns.filter=((_2>0&&_2<1)?"alpha(opacity="+(_2*100)+")":"");' + 'fin&&uu.css.opacity.set(node,%2$f)&&(ns.filter+=" %1$s");' : 'ns.opacity=fin?%2$f:_2;'), 'var gms=gain/ms,hex=uu.dmz.HEX2;' + 'ns.%s="#"+(hex[(fin?%5$d:(%5$d-%2$d)*gms+%2$d)|0]||0)+' + // (bg)color '(hex[(fin?%6$d:(%6$d-%3$d)*gms+%3$d)|0]||0)+' + '(hex[(fin?%7$d:(%7$d-%4$d)*gms+%4$d)|0]||0);', 'ns.%s=(fin?%f:%s)+"px";', // left, top, other 'var v=fin?%2$f:%3$s;v=v<0?0:v;ns.%1$s=v+"px";']; // width, height
分かってしまえば実に単純なロジックですが、このロジックに到達するまで2年程時間が必要でした。
JavaScript ってやっぱり難しいですね。