2 var X_NodeAnime_QUEUE = [],
\r
4 X_NodeAnime_reserved = false,
\r
5 X_NodeAnime_updateTimerID = 0,
\r
6 X_NodeAnime_needsDetection = false,
\r
8 X_NodeAnime_hasTransform = !!X_Node_CSS_VENDER_PREFIX[ 'transform' ],
\r
9 /* Opera mobile で translateZ(0) が有効だと XY が 0 0 になる */
\r
10 /* GPUレイヤーにいる間に要素のコンテンツを変更をすると transitionend が動かなくなるっぽい Mac safari と firefox */
\r
11 X_NodeAnime_translateZ = X_Node_CSS_VENDER_PREFIX[ 'perspective' ] && !X_UA[ 'OperaMobile' ] && !X_UA[ 'OperaTablet' ] ? ' translateZ(0)' : '',
\r
13 X_NodeAnime_transitionProps = X_NodeAnime_hasTransform ? X_Node_CSS_VENDER_PREFIX[ 'transform' ] : 'left,top';
\r
25 * TODO node.css( 'opacity' ) の上書き
\r
28 var X_NODE_ANIME_RESET = 1,
\r
29 X_NODE_ANIME_STAY_GPU = 2;
\r
32 * GPU サポートの効いたアニメーションの設定 X.Event.ANIME_START, X.Event.ANIME_END, X.Event.GPU_RELEASED
\r
33 * @alias Node.prototype.animate
\r
34 * @param {object} start { x : 0, y : 0, opacity : 1 }
\r
35 * @param {object} dest { x : 100, y : 100, opacity : 0 }
\r
36 * @param {number=} duartion アニメーション時間 ms
\r
37 * @param {string=} easing 'quadratic', 'circular', 'back', 'bounce', 'elastic'
\r
38 * @param {number=} wait GPU レイヤーの遅延解除 ms
\r
39 * @param {number=} option フォールバックについて
\r
40 * @return {Node} メソッドチェーン
\r
42 function X_Node_animate( start, dest, duration, easing, lazyRelease, option ){
\r
43 var list = X_NodeAnime_QUEUE,
\r
44 obj = this[ '_anime' ];
\r
46 if( !( this[ '_flags' ] & X_NodeFlags_IN_TREE ) ){
\r
47 alert( '@animate 要素はツリーに追加されていません!' );
\r
48 // それでもアニメーションしてタイマー代わりにするとか、、、?
\r
53 this[ '_anime' ] = obj = {
\r
54 x : X_NodeAnime_hasTransform ? 0 : NaN,
\r
55 y : X_NodeAnime_hasTransform ? 0 : NaN,
\r
58 //phase, lazyRelease, easing, follower, releaseNow
\r
62 if( 0 <= duration && X_Type_isFinite( duration ) ){
\r
63 obj.duration = duration;
\r
66 obj.easing = X_Type_isFunction( easing ) ? easing : X_NodeAnime_ease[ easing ] || X_NodeAnime_ease[ 'circular' ];
\r
69 obj.startX = obj.x = X_NodeAnime_getFinite( start[ 'x' ], obj.x );
\r
70 obj.startY = obj.y = X_NodeAnime_getFinite( start[ 'y' ], obj.y );
\r
71 obj.startA = obj.a = X_NodeAnime_getFinite( start[ 'opacity' ], obj.a );
\r
74 obj.destX = X_NodeAnime_getFinite( dest[ 'x' ], obj.x );
\r
75 obj.destY = X_NodeAnime_getFinite( dest[ 'y' ], obj.y );
\r
76 obj.destA = X_NodeAnime_getFinite( dest[ 'opacity' ], obj.a );
\r
78 obj.lazyRelease = 0 <= lazyRelease && X_Type_isFinite( lazyRelease ) ? lazyRelease : 0;
\r
81 if( !obj.duration && 6 <= obj.phase ){
\r
82 this[ 'stop' ](); // 現在値で停止
\r
85 list[ list.length ] = this;
\r
87 obj.uid = ++X_NodeAnime_uid;
\r
88 X_NodeAnime_needsDetection = true;
\r
90 if( obj.phase < 4 ){
\r
91 list.splice( list.indexOf( this ), 1 );
\r
92 list[ list.length ] = this;
\r
93 obj.uid = ++X_NodeAnime_uid;
\r
94 X_NodeAnime_needsDetection = true;
\r
97 // リストの先頭にいるため検査不要でアニメーション開始可能 4, 5, 6, 7
\r
100 // GPU 転送予約、または transform や opacity の値のみ設定
\r
101 if( obj.phase !== 5 ){ // GPU解除待ち ではない -> 4. 6, 7
\r
102 obj.phase = 4; // 強制停止(GPU転送予約)または値のみ更新
\r
103 obj.releaseNow = false; // TODO folower がいるため GPU 転送できないケースあり
\r
104 X_NodeAnime_needsDetection = true;
\r
107 if( !X_NodeAnime_reserved ){
\r
108 X_NodeAnime_reserved = true;
\r
110 if( X_Node_updateTimerID ){
\r
111 if( X_NodeAnime_updateTimerID ) X_NodeAnime_updateTimerID = X_Timer_cancelFrame( X_NodeAnime_updateTimerID );
\r
113 X_System[ 'listen' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );
\r
115 X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );
\r
116 X_NodeAnime_updateTimerID = X_Timer_requestFrame( X_NodeAnime_updateAnimations );
\r
124 function X_NodeAnime_getFinite( a, b ){
\r
125 if( a || a === 0 ) return a;
\r
126 if( b || b === 0 ) return b;
\r
131 * 1.アニメーション中の要素の停止 ->後続アニメーションの開始
\r
132 * 2.アニメーション待機中の要素の停止 -> 後続アニメーションの再調査
\r
136 * @alias Node.prototype.stop
\r
137 * @return {Node} メソッドチェーン
\r
139 function X_Node_stop( option ){
\r
140 var obj = this[ '_anime' ],
\r
141 list = X_NodeAnime_QUEUE,
\r
142 i, rm, j, xnode, _obj;
\r
144 if( !obj || !obj.phase ) return this;
\r
146 switch( obj.phase ){
\r
147 case 6 : // アニメーション開始可能 ??
\r
149 case 3 : // アニメーション待機中
\r
150 X_NodeAnime_needsDetection = true;
\r
154 case 4 : // 強制停止(GPU転送予約)
\r
155 case 7 : // アニメーション中
\r
156 if( option & X_NODE_ANIME_RESET ){
\r
157 obj.startX = obj.startY = obj.destX = obj.destY = obj.x = obj.y = X_NodeAnime_hasTransform ? 0 : NaN;
\r
158 obj.startA = obj.destA = obj.a = 1;
\r
159 }; // TODO 終了値で停止も,,,
\r
161 // obj.canceled = true;
\r
163 if( rm ) break; // 1,2,3,6 の場合ここまで
\r
169 obj.phase = 4; // 強制解除
\r
170 X_NodeAnime_needsDetection = true;
\r
172 case 5 : // GPU解除待ち
\r
173 obj.releaseNow = !( option & X_NODE_ANIME_STAY_GPU );
\r
178 list.splice( list.indexOf( this ), 1 );
\r
185 * remove(append swap 等でない部的に呼ばれている場合も), kill 時に
\r
187 function X_NodeAnime_stopNow( xnode ){
\r
188 var obj = xnode[ '_anime' ],
\r
189 flags = xnode[ '_flags' ],
\r
190 list = X_NodeAnime_QUEUE,
\r
193 // if( !obj || !obj.phase ) return; 呼び出し側で検証済
\r
195 X_NodeAnime_needsDetection = true;
\r
196 list.splice( list.indexOf( xnode ), 1 );
\r
199 // この部分 startUpdate へ?
\r
200 if( flags & ~X_Node_BitMask_RESET_GPU ){
\r
201 skipUpdate = flags & X_NodeFlags_GPU_RESERVED;
\r
202 ( flags & X_NodeFlags_GPU_RELEASE_RESERVED ) || X_NodeAnime_updatePosition( xnode, obj.x, obj.y, obj.a, false );
\r
203 skipUpdate || ( xnode[ '_rawObject' ].style.cssText = X_Node_CSS_objToCssText( xnode ) );
\r
204 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;
\r
209 * 1. 新規アニメーションが現在アニメーション中の要素の親か子であればアニメーションを待機
\r
211 function X_NodeAnime_detectWaitAnimation( xnode, duration, isTest ){
\r
212 var list = X_NodeAnime_QUEUE,
\r
215 for( ; _xnode = list[ i ]; ++i ){
\r
216 if( _xnode === xnode ) break;
\r
218 // アニメーションの優先度はリストにいる順
\r
219 // まず先行する後続待機要素の中に、親子関係のものがいないか?探す
\r
220 if( _xnode[ '_anime' ].phase <= 3 ){
\r
221 if( xnode[ 'contains' ]( _xnode ) || _xnode[ 'contains' ]( xnode ) ){ // 祖先か?見た方が早そう
\r
222 // -> いる、このような要素が複数いる場合、誰に後続すればいいか?判然としないため、準待機フラグを立てる
\r
228 // -> いない、アニメーション中(開始可能も)の要素の中に、親子関係のものがいないか?探す
\r
230 // -> いない、アニメーションが可能
\r
231 for( i = 0; _xnode = list[ i ]; ++i ){
\r
232 if( _xnode === xnode ) break;
\r
234 if( 6 <= _xnode[ '_anime' ].phase ){
\r
235 if( xnode[ 'contains' ]( _xnode ) || _xnode[ 'contains' ]( xnode ) ){
\r
236 return isTest ? 3 : _xnode;
\r
241 return duration ? 6 : 4; // duration がない場合は、アニメーション強制停止へ進みそこから GPU 解除待ちへ
\r
244 function X_NodeAnime_updateAnimations( e ){
\r
245 var list = X_NodeAnime_QUEUE,
\r
246 now = X_Timer_now(),
\r
248 i, xnode, obj, _xnode,
\r
251 if( X_NodeAnime_needsDetection ){
\r
252 X_NodeAnime_needsDetection = false;
\r
255 list.sort( X_NodeAnime_sortAnimationNode );
\r
257 for( i = 0; xnode = list[ i ]; ++i ){
\r
258 obj = xnode[ '_anime' ];
\r
260 if( obj.phase <= 3 ){
\r
261 if( !X_Type_isNumber( obj.phase = _xnode = X_NodeAnime_detectWaitAnimation( xnode, obj.duration ) ) ){
\r
262 _xnode[ '_anime' ].follower = true;
\r
263 obj.phase = 3; // 後続待機
\r
266 obj.follower = false;
\r
271 for( i = list.length; i; ){
\r
273 xnode = list[ --i ];
\r
274 obj = xnode[ '_anime' ];
\r
276 switch( obj.phase ){
\r
277 case 7 : // アニメーション中
\r
278 if( now < obj.destTime ){
\r
279 easing = obj.progress = obj.easing.fn( ( now - obj.startTime ) / obj.duration );
\r
280 X_NodeAnime_updatePosition( xnode,
\r
281 obj.x = ( obj.destX - obj.startX ) * easing + obj.startX,
\r
282 obj.y = ( obj.destY - obj.startY ) * easing + obj.startY,
\r
283 obj.a = ( obj.destA - obj.startA ) * easing + obj.startA, true );
\r
288 xnode[ 'asyncDispatch' ]( X_EVENT_ANIME_END );
\r
290 case 4 : // 強制停止(GPU転送予約)
\r
291 lazy = !obj.follower && !obj.releaseNow && obj.lazyRelease;
\r
292 X_NodeAnime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, !!lazy );
\r
294 //if( obj.canceled ){
\r
295 // xnode[ 'asyncDispatch' ]( X_EVENT_CANCELED );
\r
301 console.log( 'アニメーション終了(' + obj.phase + ') -> GPU 解除待機 ' + lazy );
\r
302 obj.releaseTime = now + lazy;
\r
303 obj.phase = 5; // GPU解除待ち
\r
306 console.log( 'アニメーション終了(' + obj.phase + ') -> ' );
\r
311 case 6 : // アニメーション開始可能
\r
312 obj.startTime = now;
\r
313 obj.destTime = now + obj.duration;
\r
314 obj.phase = 7; // アニメーション中
\r
316 xnode[ 'asyncDispatch' ]( X_EVENT_ANIME_START );
\r
318 //obj.canceled = false;
\r
319 ( obj.inited && !X_NodeAnime_translateZ ) || X_NodeAnime_updatePosition( xnode, obj.startX, obj.startY, obj.startA, true );
\r
322 case 5 : // GPU解除待ち
\r
323 if( obj.releaseTime <= now || obj.follower || obj.releaseNow ){
\r
324 X_NodeAnime_translateZ && X_NodeAnime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, false );
\r
331 default : // 2 or 3
\r
332 // 待機状態でも親要素が GPU 化していなければ、開始値をセットすることは可能
\r
333 obj.inited || X_NodeAnime_updatePosition( xnode, obj.startX, obj.startY, obj.startA, false );
\r
334 obj.inited = false;
\r
338 obj.releaseNow = false;
\r
341 X_NodeAnime_translateZ && xnode[ 'asyncDispatch' ]( X_EVENT_GPU_RELEASED );
\r
343 if( obj.follower ) X_NodeAnime_needsDetection = c = true;
\r
344 list.splice( i, 1 );
\r
349 //c && console.log( 'anime... ' + X_Node_updateTimerID );
\r
351 if( X_NodeAnime_reserved = c ){
\r
352 if( X_Node_updateTimerID ){
\r
353 // scrollbox では X_System X_EVENT_UPDATED は不可。。。
\r
354 !e || e.type !== X_EVENT_UPDATED ?
\r
355 X_System[ 'listen' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations ) :
\r
356 X_NodeAnime_updateTimerID && X_Timer_cancelFrame( X_NodeAnime_updateTimerID );
\r
357 X_NodeAnime_updateTimerID = 0;
\r
359 X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );
\r
360 X_NodeAnime_updateTimerID = X_Timer_requestFrame( X_NodeAnime_updateAnimations );
\r
363 X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );
\r
364 X_NodeAnime_updateTimerID = 0;
\r
369 * アニメーション開始、アニメーション中、強制停止(GPU転送予約)、GPU解除待ち の要素をリストの先頭に
\r
371 function X_NodeAnime_sortAnimationNode( xnode1, xnode2 ){
\r
372 var a = 4 <= xnode1[ '_anime' ].phase,
\r
373 b = 4 <= xnode2[ '_anime' ].phase;
\r
375 if( ( a && b ) && ( !a && !b ) ){ // Chrome のみ
\r
376 return xnode1[ '_anime' ].uid - xnode2[ '_anime' ].uid;
\r
381 function X_NodeAnime_updatePosition( xnode, x, y, opacity, useGPU ){
\r
382 //console.log( 'updatePosition x:' + x + ' gpu:' + !!useGPU );
\r
383 if( X_NodeAnime_hasTransform ){
\r
385 transform : 'translate(' + ( x | 0 ) + 'px,' + ( y | 0 ) + 'px)' + ( useGPU ? X_NodeAnime_translateZ : '' ),
\r
389 x === x && xnode[ 'css' ]( 'left', ( x | 0 ) + 'px' );
\r
390 y === y && xnode[ 'css' ]( 'top', ( y | 0 ) + 'px' );
\r
391 opacity === opacity && xnode[ 'css' ]( 'opacity', opacity );
\r
394 if( X_NodeAnime_translateZ ){
\r
396 if( xnode[ '_flags' ] & X_NodeFlags_GPU_RELEASE_RESERVED ){
\r
397 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;
\r
398 xnode[ '_flags' ] |= X_NodeFlags_GPU_NOW;
\r
400 if( !( xnode[ '_flags' ] & X_NodeFlags_GPU_NOW ) ){
\r
401 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;
\r
402 xnode[ '_flags' ] |= X_NodeFlags_GPU_RESERVED;
\r
405 if( xnode[ '_flags' ] & X_NodeFlags_GPU_NOW ){
\r
406 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;
\r
407 xnode[ '_flags' ] |= X_NodeFlags_GPU_RELEASE_RESERVED;
\r
409 if( xnode[ '_flags' ] & X_NodeFlags_GPU_RESERVED ){
\r
410 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;
\r
418 X_NodeAnime_ease = {
\r
420 style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
\r
422 return k * ( 2 - k );
\r
426 style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
\r
428 return Math.sqrt( 1 - ( --k * k ) );
\r
432 style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
\r
435 return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
\r
441 if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
\r
442 return 7.5625 * k * k;
\r
443 } else if ( k < ( 2 / 2.75 ) ) {
\r
444 return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
\r
445 } else if ( k < ( 2.5 / 2.75 ) ) {
\r
446 return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
\r
448 return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
\r
458 if ( k === 0 ) { return 0; }
\r
459 if ( k == 1 ) { return 1; }
\r
461 return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
\r