2 * GPU レイヤーを使うとスムースなアニメーションが得られる
\r
3 * 但し、古めのデバイスでは処理落ちが起こる
\r
4 * 処理落ちは、最も古い初代 iPod touch (iOS3) では特に顕著に発生するため、テスト機としてちょうどいい
\r
5 * css の書き方によっても、処理落ちやガタツキに影響するので、デザイナーが気軽に触れるものではない
\r
7 * transform の登場によって、要素の表示位置を変更する方法が、css-p との2択になった。
\r
8 * 初代 iPod touch では、簡単な css-p すら無視してくれるので、積極的に transform を使っていけ、ということだと思う。
\r
9 * 但し transform には様々な癖があり、一筋縄ではいかない。
\r
11 * DHTML を扱う専用モジュールを用意して、そこにコードとノウハウを集積する。
\r
14 * 新しいアニメーション・キューが追加されたときに、現在アニメーションしている要素と親子関係ならば、以前のアニメーションは直ちに停止し、GPUレイヤーを解除。
\r
17 X.Dom.Anime = (function( window, document, navigator, Math, undefined ){
\r
19 var QUEUE_LIST = [],
\r
21 ABS = new Function( 'v', 'return v<0?-v:v' ),
\r
22 animeEnabled = !X.UA.EInk,
\r
25 has3d = X.Dom.Style.has3d,
\r
26 transform = X.Dom.Style.transform,
\r
27 transformOrigin = X.Dom.Style.transformOrigin,
\r
28 translateZ = X.Dom.Style.translateZ,
\r
29 transition = X.Dom.Style.transition;
\r
32 * 新規アニメーション要素が現在アニメーションしている要素と親子関係の場合、
\r
33 * 親子関係のアニメーションを停止して一挙に最後の状態へ
\r
35 function registerQueue( queue ){
\r
36 var list = QUEUE_LIST,
\r
38 xnode = queue.xnode,
\r
43 _xnode !== xnode && ( _xnode.contains( xnode ) || xnode.contains( _xnode ) ) && q.stop( true );
\r
45 list[ list.length ] = queue;
\r
46 !timerID && ( timerID = X.Timer.nextFrame( onEnterFrame ) );
\r
49 function unregisterQueue( queue ){
\r
50 var i = QUEUE_LIST.indexOf( queue );
\r
51 if( i === -1 ) return;
\r
52 if( QUEUE_LIST.length === 1 ){
\r
53 QUEUE_LIST.length = 0;
\r
54 timerID && X.Timer.cancelFrame( onEnterFrame );
\r
56 QUEUE_LIST.splice( i, 1 );
\r
60 function onEnterFrame(){
\r
61 var list = QUEUE_LIST,
\r
66 list[ --l ].tick( t );
\r
68 list.length && ( timerID = X.Timer.nextFrame( onEnterFrame ) );
\r
71 var AnimationQueue = function( xnode ){
\r
75 X.Class._override( AnimationQueue.prototype,
\r
77 state : 0, // 0:停止, 1:登録済, 2:アニメ中の親子要素のGPUレイヤー解除待ち, 3:アニメ中;GPUレイヤーにセット、目標値のセット(transisiton有効), 4:
\r
82 init : function( data, duration, timmingFunction ){
\r
83 var list, type, prop, data, target, i, l;
\r
84 if( !( list = this.list ) ) this.list = list = [];
\r
85 for( type in data ){
\r
86 for( i = 0, l = list.length; i < l; ++i ){
\r
87 if( list[ i ][ 0 ] === type ){
\r
96 prop[ 2 ] = this.xnode.width(); // currentValue
\r
99 prop[ 2 ] = this.xnode.height(); // currentValue
\r
103 type = transform ? 'translateX' : 'left';
\r
104 prop[ 2 ] = this.xnode.x(); // currentValue
\r
108 type = transform ? 'translateY' : 'top';
\r
109 prop[ 2 ] = this.xnode.y(); // currentValue
\r
114 prop[ 1 ] = data[ type ]; // target value;
\r
115 prop[ 3 ] = prop[ 1 ] - prop[ 2 ];
\r
118 this.state === 0 && registerQueue( this );
\r
121 this.startTime = X.getTime();
\r
122 this.duration = duration;
\r
124 this.timming = 'liner';//timmingFunction;
\r
126 this.timming = Liner;
\r
130 stop : function( opt_goEndPosition ){
\r
131 if( this.xnode.dispatch( { type : X.UI.Event.ANIME_BEFORE_STOP, target : this.xnode } ) & X.Callback.PREVENT_DEFAULT ){
\r
134 this.xnode.dispatch( { type : X.UI.Event.ANIME_STOP, target : this.xnode } );
\r
135 opt_goEndPosition && this.tick( this.startTime + this.duration );
\r
136 unregisterQueue( this );
\r
141 var ratio = ( time - this.startTime ) / this.duration,
\r
143 xnode = this.xnode,
\r
145 if( this.state === 1 ){
\r
147 calculateCSS( css = {}, list );
\r
148 css[ transformOrigin ] = '0 0';
\r
149 css[ transition.Property ] = this._transProp;
\r
150 css[ transition.Delay ] = '0';
\r
151 css[ transition.Duration ] = this.duration + 'ms';
\r
152 css[ transition.TimingFunction ] = this.timming;
\r
154 delete this._transProp;
\r
156 if( this.state === 2 ){
\r
159 css[ transform ] = this._transform + translateZ;
\r
161 .listenOnce( transition.End, xnode, this.onTransitionEnd )
\r
162 .dispatch( { type : X.UI.Event.ANIME_START, ratio : 1, target : xnode } );
\r
163 this.startTime = time;
\r
167 css[ transform ] = this._transform; // GPU support off
\r
168 delete this._transform;
\r
171 .unlisten( transition.End, xnode, onTransitionEnd )
\r
172 .dispatch( { type : X.UI.Event.ANIME, ratio : 1, target : xnode } );
\r
173 xnode.dispatch( { type : X.UI.Event.ANIME_END, ratio : 1, target : xnode } );
\r
175 xnode.dispatch( { type : X.UI.Event.ANIME, ratio : rario, target : xnode } );
\r
179 var ratio = ( time - this.startTime ) / this.duration,
\r
181 xnode = this.xnode,
\r
185 calculateCSS( css = {}, list, 1, time - this.startTime, this.duration );
\r
188 .dispatch( { type : X.UI.Event.ANIME, ratio : 1, target : xnode } );
\r
189 xnode.dispatch( { type : X.UI.Event.ANIME_END, ratio : 1, target : xnode } );
\r
192 calculateCSS( css = {}, list, ratio, time - this.startTime, this.duration );
\r
194 .dispatch( { type : X.UI.Event.ANIME, ratio : rario, target : xnode } );
\r
197 onTransitionEnd : transition && (function(){
\r
198 return this.dispatch( { type : X.UI.Event.ANIME_END, ratio : 1, target : xnode } );
\r
201 * transisitonProperty を集める
\r
206 (function( css, list ){
\r
210 data, type, transX, transY, _transX, _transY;
\r
211 for( ; i < l; ++i ){
\r
215 case 'translateX' :
\r
216 transX = data[ 2 ];
\r
217 _transX = data[ 1 ];
\r
219 case 'translateY' :
\r
220 transY = data[ 2 ];
\r
221 _transY = data[ 1 ];
\r
224 css[ type ] = data[ 1 ];
\r
225 prop[ prop.length ] = type;
\r
228 if( transX || transY ){
\r
229 prop[ prop.length ] = cssVendor + 'transform';
\r
230 if( transX && transY ){
\r
231 css[ transform ] = cssVendor + 'translate(' + transX + ',' + transY + ')' + translateZ;
\r
232 this._transform = cssVendor + 'translate(' + _transX + ',' + _transY + ')';
\r
236 css[ transform ] = cssVendor + 'translateX(' + transX + ')' + translateZ;
\r
237 this._transform = cssVendor + 'translateX(' + _transX + ')';
\r
240 css[ transform ] = cssVendor + 'translateY(' + transY + ')' + translateZ;
\r
241 this._transform = cssVendor + 'translateY(' + _transY + ')';
\r
244 this._transProp = prop.join( ',' );
\r
247 (function( css, list, ratio, time, duration ){
\r
248 var i = 0, l = list.length,
\r
249 easing = this.timming,
\r
250 data, type, transX, transY;
\r
251 for( ; i < l; ++i ){
\r
255 case 'translateX' :
\r
256 transX = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
258 case 'translateY' :
\r
259 transY = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
262 css[ type ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
265 if( transX && transY ){
\r
266 this._transform = cssVendor + 'translate(' + transX + ',' + transY + ')';
\r
267 css[ transform ] = this._transform + translateZ;
\r
270 this._transform = cssVendor + 'translateX(' + transX + ')';
\r
271 css[ transform ] = this._transform + translateZ;
\r
274 this._transform = cssVendor + 'translateY(' + transY + ')';
\r
275 css[ transform ] = this._transform + translateZ;
\r
278 (function( css, list, l, ratio, time, duration ){
\r
280 easing = this.timming;
\r
282 data = list[ --l ];
\r
283 css[ data[ 0 ] ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
288 function Liner( t, b, c, d ){
\r
289 return c * t / d + b;
\r
293 // https://github.com/arian/cubic-bezier/blob/master/index.js, MIT License
\r
294 function CubicBezier(x1, y1, x2, y2, t, epsilon){
\r
295 var x = t, t0 = 0, t1 = 1, t2 = x, d2, i = 0;
\r
297 // First try a few iterations of Newton's method -- normally very fast.
\r
298 for (; i < 8; i++){
\r
299 x2 = CubicBezier.curveX(x1,x2,t2) - x;
\r
300 if (ABS(x2) < epsilon) return CubicBezier.curveY(y1, y2, t2);
\r
301 d2 = CubicBezier.derivativeCurveX( x1, x2, t2);
\r
302 if (ABS(d2) < 1e-6) break;
\r
305 if (x < t0) return CubicBezier.curveY(y1, y2, t0);
\r
306 if (x > t1) return CubicBezier.curveY(y1, y2, t1);
\r
310 // Fallback to the bisection method for reliability.
\r
312 x2 = CubicBezier.curveX(x1,x2,t2);
\r
313 if (ABS(x2 - x) < epsilon) return CubicBezier.curveY(y1, y2, t2);
\r
314 if (x > x2) t0 = t2;
\r
316 t2 = (t1 - t0) * .5 + t0;
\r
319 return CubicBezier.curveY(y1, y2, t2);
\r
322 CubicBezier.curveX = function( x1, x2, t ){
\r
324 return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
\r
326 CubicBezier.curveY = function( y1, y2, t ){
\r
328 return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
\r
330 CubicBezier.derivativeCurveX = function( x1, x2, t ){
\r
332 return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2;
\r
338 X.Dom.Node.prototype.anime = function(){
\r
339 if( !this._anime ) this._anime = new AnimationQueue( this );
\r
340 this._anime.init.apply( this._anime, arguments );
\r
343 X.Dom.Node.prototype.stop = function(){
\r
344 if( !this._anime ) return this;
\r
345 this._anime.stop.apply( this._anime, arguments );
\r
359 })( window, document, navigator, Math );
\r