OSDN Git Service

Verison 0.6.201, X.UI.ScrollBox woring at IE5.5 well.
[pettanr/clientJs.git] / 0.6.x / js / 02_dom / 10_XNodeAnime.js
1 \r
2 var X_NodeAnime_QUEUE           = [],\r
3         X_NodeAnime_uid             = 0,\r
4         X_NodeAnime_reserved        = false,\r
5         X_NodeAnime_updateTimerID   = 0,\r
6         X_NodeAnime_needsDetection  = false,\r
7         \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
12 \r
13         X_NodeAnime_transitionProps = X_NodeAnime_hasTransform ? X_Node_CSS_VENDER_PREFIX[ 'transform' ] : 'left,top';\r
14 \r
15 /*\r
16  * phase:\r
17  *  0: アニメーション無\r
18  *  1: 登録されたばかり\r
19  *  2: 準待機\r
20  *  3: 後続待機\r
21  *  4: 強制停止(GPU転送予約)\r
22  *  5: GPU解除待ち\r
23  *  6: 開始可能\r
24  *  7: アニメーション中\r
25  * TODO node.css( 'opacity' ) の上書き\r
26  */\r
27 \r
28 var X_NODE_ANIME_RESET = 1,\r
29         X_NODE_ANIME_STAY_GPU = 2;\r
30 \r
31 /**\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
41  */\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
45         \r
46         if( !( this[ '_flags' ] & X_NodeFlags_IN_TREE ) ){\r
47                 alert( '@animate 要素はツリーに追加されていません!' );\r
48                 // それでもアニメーションしてタイマー代わりにするとか、、、?\r
49                 return this;\r
50         };\r
51         \r
52         if( !obj ){\r
53                 this[ '_anime' ] = obj = {\r
54                                 x : X_NodeAnime_hasTransform ? 0 : NaN,\r
55                                 y : X_NodeAnime_hasTransform ? 0 : NaN,\r
56                                 a : 1,\r
57                                 duration : 0\r
58                                 //phase, lazyRelease, easing, follower, releaseNow\r
59                         };\r
60         };\r
61         \r
62         if( 0 <= duration && X_Type_isFinite( duration ) ){\r
63                 obj.duration  = duration;\r
64         };\r
65 \r
66         obj.easing = X_Type_isFunction( easing ) ? easing : X_NodeAnime_ease[ easing ] || X_NodeAnime_ease[ 'circular' ];\r
67                 \r
68 // form :\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
72 \r
73  // to :\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
77 \r
78         obj.lazyRelease = 0 <= lazyRelease && X_Type_isFinite( lazyRelease ) ? lazyRelease : 0;\r
79         obj.inited = false;\r
80 \r
81         if( !obj.duration && 6 <= obj.phase ){\r
82                 this[ 'stop' ](); // 現在値で停止\r
83         } else {\r
84                 if( !obj.phase ){\r
85                         list[ list.length ] = this;\r
86                         obj.phase = 1;\r
87                         obj.uid   = ++X_NodeAnime_uid;\r
88                         X_NodeAnime_needsDetection = true;\r
89                 } else\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
95                 } else\r
96                 if( obj.duration ){\r
97                         // リストの先頭にいるため検査不要でアニメーション開始可能 4, 5, 6, 7\r
98                         obj.phase = 6;\r
99                 } else\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
105                 };\r
106                 \r
107                 if( !X_NodeAnime_reserved ){\r
108                         X_NodeAnime_reserved = true;\r
109                         \r
110                         if( X_Node_updateTimerID ){\r
111                                 if( X_NodeAnime_updateTimerID ) X_NodeAnime_updateTimerID = X_Timer_cancelFrame( X_NodeAnime_updateTimerID );\r
112 \r
113                                 X_System[ 'listen' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
114                         } else {\r
115                                 X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
116                                 X_NodeAnime_updateTimerID = X_Timer_requestFrame( X_NodeAnime_updateAnimations );\r
117                         };\r
118                 };      \r
119         };\r
120 \r
121         return this;\r
122 };\r
123 \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
127         return NaN;\r
128 };\r
129 \r
130 /*\r
131  * 1.アニメーション中の要素の停止 ->後続アニメーションの開始\r
132  * 2.アニメーション待機中の要素の停止 -> 後続アニメーションの再調査\r
133  */\r
134 /**\r
135  * アニメーションの停止。\r
136  * @alias Node.prototype.stop\r
137  * @return {Node} メソッドチェーン\r
138  */\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
143         \r
144         if( !obj || !obj.phase ) return this;\r
145 \r
146         switch( obj.phase ){\r
147                 case 6 : // アニメーション開始可能 ??\r
148                 case 2 : // 準待機\r
149                 case 3 : // アニメーション待機中\r
150                         X_NodeAnime_needsDetection = true;\r
151                 case 1 :\r
152                         rm = true;\r
153 \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
160                         \r
161                         // obj.canceled = true;\r
162                         \r
163                         if( rm ) break; // 1,2,3,6 の場合ここまで\r
164                 \r
165                         obj.destX = obj.x;\r
166                         obj.destY = obj.y;\r
167                         obj.destA = obj.a;\r
168 \r
169                         obj.phase = 4; // 強制解除\r
170                         X_NodeAnime_needsDetection = true;\r
171                         \r
172                 case 5 : // GPU解除待ち\r
173                         obj.releaseNow = !( option & X_NODE_ANIME_STAY_GPU );\r
174                         break;\r
175         };\r
176 \r
177         if( rm ){\r
178                 list.splice( list.indexOf( this ), 1 );\r
179                 obj.phase = 0;  \r
180         };\r
181 \r
182         return this;\r
183 };\r
184 /*\r
185  * remove(append swap 等でない部的に呼ばれている場合も), kill 時に\r
186  */\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
191                 skipUpdate;\r
192         \r
193         // if( !obj || !obj.phase ) return; 呼び出し側で検証済\r
194 \r
195         X_NodeAnime_needsDetection = true;\r
196         list.splice( list.indexOf( xnode ), 1 );\r
197         obj.phase = 0;\r
198 \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
205         };\r
206 };\r
207 \r
208 /*\r
209  * 1. 新規アニメーションが現在アニメーション中の要素の親か子であればアニメーションを待機\r
210  */\r
211 function X_NodeAnime_detectWaitAnimation( xnode, duration, isTest ){\r
212         var list = X_NodeAnime_QUEUE,\r
213                 i    = 0, _xnode;\r
214         \r
215         for( ; _xnode = list[ i ]; ++i ){\r
216                 if( _xnode === xnode ) break;\r
217                 \r
218                 // アニメーションの優先度はリストにいる順\r
219                 // まず先行する後続待機要素の中に、親子関係のものがいないか?探す\r
220                 if( _xnode[ '_anime' ].phase <= 3 ){\r
221                         if( xnode[ 'contains' ]( _xnode ) || _xnode[ 'contains' ]( xnode ) ){ // 祖先か?見た方が早そう\r
222                                 // -> いる、このような要素が複数いる場合、誰に後続すればいいか?判然としないため、準待機フラグを立てる\r
223                                 return 2;\r
224                         };\r
225                 };\r
226         };\r
227 \r
228         // -> いない、アニメーション中(開始可能も)の要素の中に、親子関係のものがいないか?探す\r
229         //           -> いる、待機状態へ\r
230         //           -> いない、アニメーションが可能\r
231         for( i = 0; _xnode = list[ i ]; ++i ){\r
232                 if( _xnode === xnode ) break;\r
233 \r
234                 if( 6 <= _xnode[ '_anime' ].phase ){\r
235                         if( xnode[ 'contains' ]( _xnode ) || _xnode[ 'contains' ]( xnode ) ){\r
236                                 return isTest ? 3 : _xnode;\r
237                         };\r
238                 };\r
239         };\r
240         // アニメーション可能\r
241         return duration ? 6 : 4; // duration がない場合は、アニメーション強制停止へ進みそこから GPU 解除待ちへ\r
242 };\r
243 \r
244 function X_NodeAnime_updateAnimations( e ){\r
245         var list = X_NodeAnime_QUEUE,\r
246                 now  = X_Timer_now(),\r
247                 c    = false,\r
248                 i, xnode, obj, _xnode,\r
249                 rm, easing, lazy;\r
250         \r
251         if( X_NodeAnime_needsDetection ){\r
252                 X_NodeAnime_needsDetection = false;\r
253                 \r
254                 //\r
255                 list.sort( X_NodeAnime_sortAnimationNode );\r
256                 \r
257                 for( i = 0; xnode = list[ i ]; ++i ){\r
258                         obj = xnode[ '_anime' ];\r
259                         \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
264                                 };\r
265                         } else {\r
266                                 obj.follower = false;\r
267                         };\r
268                 };\r
269         };\r
270         \r
271         for( i = list.length; i; ){\r
272                 rm    = false;\r
273                 xnode = list[ --i ];\r
274                 obj   = xnode[ '_anime' ];\r
275 \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
284                                         c = true;\r
285                                         break;\r
286                                 };\r
287                                 // アニメーション終了\r
288                                 xnode[ 'asyncDispatch' ]( X_EVENT_ANIME_END );\r
289                                 \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
293 \r
294                                 //if( obj.canceled ){\r
295                                 //      xnode[ 'asyncDispatch' ]( X_EVENT_CANCELED );\r
296                                 //} else {\r
297                                         \r
298                                 //};\r
299                                 \r
300                                 if( lazy ){\r
301                                         console.log( 'アニメーション終了(' + obj.phase + ') -> GPU 解除待機 ' + lazy );\r
302                                         obj.releaseTime = now + lazy;\r
303                                         obj.phase = 5; // GPU解除待ち\r
304                                         c = true;\r
305                                 } else {\r
306                                         console.log( 'アニメーション終了(' + obj.phase + ') -> ' );\r
307                                         rm = true;\r
308                                 };\r
309                                 break;\r
310 \r
311                         case 6 : // アニメーション開始可能\r
312                                 obj.startTime = now;\r
313                                 obj.destTime  = now + obj.duration;\r
314                                 obj.phase     = 7; // アニメーション中\r
315                                 obj.progress  = 0;                                      \r
316                                 xnode[ 'asyncDispatch' ]( X_EVENT_ANIME_START );\r
317                                 c = true;\r
318                                 //obj.canceled  = false;\r
319                                 ( obj.inited && !X_NodeAnime_translateZ ) || X_NodeAnime_updatePosition( xnode, obj.startX, obj.startY, obj.startA, true );\r
320                                 break;\r
321                         \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
325                                         rm = true;\r
326                                 } else {\r
327                                         c = true;\r
328                                 };\r
329                                 break;\r
330                         \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
335                                 break;\r
336                 };\r
337                 \r
338                 obj.releaseNow = false;\r
339                 \r
340                 if( rm ){\r
341                         X_NodeAnime_translateZ && xnode[ 'asyncDispatch' ]( X_EVENT_GPU_RELEASED );\r
342                         // 後続のアニメーションがある場合\r
343                         if( obj.follower ) X_NodeAnime_needsDetection = c = true;\r
344                         list.splice( i, 1 );\r
345                         obj.phase = 0;\r
346                 };\r
347         };\r
348         \r
349         //c && console.log( 'anime... ' + X_Node_updateTimerID );\r
350         \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
358                 } else {\r
359                         X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
360                         X_NodeAnime_updateTimerID = X_Timer_requestFrame( X_NodeAnime_updateAnimations );\r
361                 };\r
362         } else {\r
363                 X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
364                 X_NodeAnime_updateTimerID = 0;\r
365         };\r
366 };\r
367 \r
368 /*\r
369  * アニメーション開始、アニメーション中、強制停止(GPU転送予約)、GPU解除待ち の要素をリストの先頭に\r
370  */\r
371 function X_NodeAnime_sortAnimationNode( xnode1, xnode2 ){\r
372         var a = 4 <= xnode1[ '_anime' ].phase,\r
373                 b = 4 <= xnode2[ '_anime' ].phase;\r
374         \r
375     if( ( a && b ) && ( !a && !b ) ){ // Chrome のみ\r
376         return xnode1[ '_anime' ].uid - xnode2[ '_anime' ].uid;\r
377     };\r
378     return a ? -1 : 1;\r
379 };\r
380 \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
384                 xnode[ 'css' ]({\r
385                         transform : 'translate(' + ( x | 0 ) + 'px,' + ( y | 0 ) + 'px)' + ( useGPU ? X_NodeAnime_translateZ : '' ),\r
386                         opacity   : opacity\r
387                 });\r
388         } else {\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
392         };\r
393 \r
394         if( X_NodeAnime_translateZ ){\r
395                 if( useGPU ){\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
399                         } else\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
403                         };\r
404                 } else {\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
408                         } else\r
409                         if( xnode[ '_flags' ] & X_NodeFlags_GPU_RESERVED ){\r
410                                 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;\r
411                         };\r
412                 };              \r
413         };\r
414 };\r
415 \r
416 \r
417 var\r
418         X_NodeAnime_ease = {\r
419                 'quadratic' : {\r
420                         style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',\r
421                         fn: function (k) {\r
422                                 return k * ( 2 - k );\r
423                         }\r
424                 },\r
425                 'circular' : {\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
427                         fn: function (k) {\r
428                                 return Math.sqrt( 1 - ( --k * k ) );\r
429                         }\r
430                 },\r
431                 'back' : {\r
432                         style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',\r
433                         fn: function (k) {\r
434                                 var b = 4;\r
435                                 return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;\r
436                         }\r
437                 },\r
438                 'bounce' : {\r
439                         style: '',\r
440                         fn: function (k) {\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
447                                 } else {\r
448                                         return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;\r
449                                 }\r
450                         }\r
451                 },\r
452                 'elastic' : {\r
453                         style: '',\r
454                         fn: function (k) {\r
455                                 var f = 0.22,\r
456                                         e = 0.4;\r
457 \r
458                                 if ( k === 0 ) { return 0; }\r
459                                 if ( k == 1 ) { return 1; }\r
460 \r
461                                 return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );\r
462                         }\r
463                 }\r
464         };\r