OSDN Git Service

6ba1bc2b006d9b0be8ba086e2875be8ab86091b5
[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                 isNew = !obj,\r
46                 now, stop;\r
47         \r
48         if( !( this[ '_flags' ] & X_NodeFlags_IN_TREE ) ){\r
49                 alert( '@animate 要素はツリーに追加されていません!' );\r
50                 // それでもアニメーションしてタイマー代わりにするとか、、、?\r
51                 return this;\r
52         };\r
53         \r
54         if( isNew ){\r
55                 this[ '_anime' ] = obj = {\r
56                                 x : 0, y : 0, a : 1,\r
57                                 destX : 0, destY : 0, destA : 1,\r
58                                 duration : 0\r
59                                 //phase, lazyRelease, easing, follower, releaseNow\r
60                         };\r
61         };\r
62         \r
63         if( 0 <= duration && X_Type_isFinite( duration ) ){\r
64                 obj.duration  = duration;\r
65                 stop = !duration;\r
66         };\r
67 \r
68         if( obj.phase !== 7 ){\r
69                 // アニメーション中は easing の変更ができない\r
70                 obj.easing = X_Type_isFunction( easing ) ? easing : X_NodeAnime_ease[ easing ] || X_NodeAnime_ease[ 'circular' ];\r
71         };\r
72 // form :\r
73         obj.startX = obj.x = X_NodeAnime_getFinite( start[ 'x' ],       obj.x );\r
74         obj.startY = obj.y = X_NodeAnime_getFinite( start[ 'y' ],       obj.y );\r
75         obj.startA = obj.a = X_NodeAnime_getFinite( start[ 'opacity' ], obj.a );\r
76 \r
77  // to :\r
78         obj.destX     = X_NodeAnime_getFinite( dest[ 'x' ],       obj.destX );\r
79         obj.destY     = X_NodeAnime_getFinite( dest[ 'y' ],       obj.destY );\r
80         obj.destA     = X_NodeAnime_getFinite( dest[ 'opacity' ], obj.destA );\r
81 \r
82         obj.lazyRelease = 0 <= lazyRelease && X_Type_isFinite( lazyRelease ) ? lazyRelease : 0;\r
83 \r
84         if( stop && 6 <= obj.phase ){\r
85                 this[ 'stop' ](); // 現在値で停止\r
86         } else\r
87         if( !stop || obj.lazyRelease ){\r
88                 if( isNew || !obj.phase ){\r
89                         list[ list.length ] = this;\r
90                         obj.phase = 1;\r
91                         obj.uid   = ++X_NodeAnime_uid;\r
92                         X_NodeAnime_needsDetection = true;\r
93                 } else\r
94                 if( obj.phase < 4 ){\r
95                         list.splice( list.indexOf( this ), 1 );\r
96                         list[ list.length ] = this;\r
97                         obj.uid   = ++X_NodeAnime_uid;\r
98                         X_NodeAnime_needsDetection = true;\r
99                 } else\r
100                 if( !stop ){\r
101                         // リストの先頭にいるため検査不要でアニメーション開始可能 4, 5, 6, 7\r
102                         obj.phase = 6;\r
103                 } else\r
104                 // obj.lazyRelease はあり\r
105                 if( obj.phase !== 5 ){ // GPU解除待ち ではない -> 4. 6, 7\r
106                         obj.phase      = 4; // 強制停止(GPU転送予約)\r
107                         obj.releaseNow = false; // TODO folower が要るため GPU 転送できないケースあり\r
108                         X_NodeAnime_needsDetection = true;\r
109                 };\r
110                 \r
111                 if( !X_NodeAnime_reserved ){\r
112                         X_NodeAnime_reserved = true;\r
113                         \r
114                         if( X_Node_updateTimerID ){\r
115                                 if( X_NodeAnime_updateTimerID ) X_NodeAnime_updateTimerID = X_Timer_cancelFrame( X_NodeAnime_updateTimerID );\r
116 \r
117                                 X_System[ 'listen' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
118                         } else {\r
119                                 X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
120                                 X_NodeAnime_updateTimerID = X_Timer_requestFrame( X_NodeAnime_updateAnimations );\r
121                         };\r
122                 };      \r
123         } else {\r
124                 if( obj.phase ){\r
125                         this[ 'stop' ](); // リストから外す\r
126                 };\r
127                 console.log( 'リストに加えない!' );\r
128         };\r
129 \r
130         return this;\r
131 };\r
132 \r
133 function X_NodeAnime_getFinite( a, b ){\r
134         if( a || a === 0 ) return a;\r
135         if( b || b === 0 ) return b;\r
136         return NaN;\r
137 };\r
138 \r
139 /*\r
140  * 1.アニメーション中の要素の停止 ->後続アニメーションの開始\r
141  * 2.アニメーション待機中の要素の停止 -> 後続アニメーションの再調査\r
142  */\r
143 /**\r
144  * アニメーションの停止。\r
145  * @alias Node.prototype.stop\r
146  * @return {Node} メソッドチェーン\r
147  */\r
148 function X_Node_stop( option ){\r
149         var obj    = this[ '_anime' ],\r
150                 list   = X_NodeAnime_QUEUE,\r
151                 i, rm, j, xnode, _obj;\r
152         \r
153         if( !obj || !obj.phase ) return this;\r
154 \r
155         switch( obj.phase ){\r
156                 case 6 : // アニメーション開始可能 ??\r
157                 case 2 : // 準待機\r
158                 case 3 : // アニメーション待機中\r
159                         X_NodeAnime_needsDetection = true;\r
160                 case 1 :\r
161                         rm = true;\r
162 \r
163                 case 4 : // 強制停止(GPU転送予約)\r
164                 case 7 : // アニメーション中\r
165                         if( option & X_NODE_ANIME_RESET ){\r
166                                 obj.startX = obj.startY = obj.destX = obj.destY = obj.x = obj.y = 0;\r
167                                 obj.startA = obj.destA = obj.a = 1;\r
168                         }; // TODO 終了値で停止も,,,\r
169                         \r
170                         if( rm ) break; // 1,2,3,6 の場合ここまで\r
171                 \r
172                         obj.destX = obj.x;\r
173                         obj.destY = obj.y;\r
174                         obj.destA = obj.a;\r
175 \r
176                         obj.phase = 4; // 強制解除\r
177                         X_NodeAnime_needsDetection = true;\r
178                         \r
179                 case 5 : // GPU解除待ち\r
180                         obj.releaseNow = !( option & X_NODE_ANIME_STAY_GPU );\r
181                         break;\r
182         };\r
183 \r
184         if( rm ){\r
185                 list.splice( list.indexOf( this ), 1 );\r
186                 obj.phase = 0;  \r
187         };\r
188 \r
189         return this;\r
190 };\r
191 \r
192 /*\r
193  * 1. 新規アニメーションが現在アニメーション中の要素の親か子であればアニメーションを待機\r
194  * 2. \r
195  */\r
196 function X_NodeAnime_detectWaitAnimation( xnode, duration, isText ){\r
197         var list = X_NodeAnime_QUEUE,\r
198                 i    = 0, _xnode;\r
199         \r
200         for( ; _xnode = list[ i ]; ++i ){\r
201                 if( _xnode === xnode ) break;\r
202                 \r
203                 // アニメーションの優先度はリストにいる順\r
204                 // まず先行する後続待機要素の中に、親子関係のものがいないか?探す\r
205                 if( _xnode[ '_anime' ].phase <= 3 ){\r
206                         if( xnode[ 'contains' ]( _xnode ) || _xnode[ 'contains' ]( xnode ) ){\r
207                                 // -> いる、このような要素が複数いる場合、誰に後続すればいいか?判然としないため、準待機フラグを立てる\r
208                                 return 2;\r
209                         };\r
210                 };\r
211         };\r
212 \r
213         // -> いない、アニメーション中(開始可能も)の要素の中に、親子関係のものがいないか?探す\r
214         //           -> いる、待機状態へ\r
215         //           -> いない、アニメーションが可能\r
216         for( i = 0; _xnode = list[ i ]; ++i ){\r
217                 if( _xnode === xnode ) break;\r
218 \r
219                 if( 6 <= _xnode[ '_anime' ].phase ){\r
220                         if( xnode[ 'contains' ]( _xnode ) || _xnode[ 'contains' ]( xnode ) ){\r
221                                 return isText ? 3 : _xnode;\r
222                         };\r
223                 };\r
224         };\r
225         // アニメーション可能\r
226         return duration ? 6 : 4; // duration がない場合は、アニメーション強制停止へ進みそこから GPU 解除待ちへ\r
227 };\r
228 \r
229 function X_NodeAnime_updateAnimations( e ){\r
230         var list = X_NodeAnime_QUEUE,\r
231                 i    = list.length,\r
232                 now  = X_Timer_now(),\r
233                 c = false,\r
234                 i, xnode, obj, _xnode,\r
235                 rm, easing, follower, lazy;\r
236         \r
237         if( X_NodeAnime_needsDetection ){\r
238                 X_NodeAnime_needsDetection = false;\r
239                 \r
240                 //\r
241                 list.sort( X_NodeAnime_sortAnimationNode );             \r
242                 \r
243                 for( i = 0; xnode = list[ i ]; ++i ){\r
244                         obj = xnode[ '_anime' ];\r
245                         \r
246                         if( obj.phase <= 3 ){\r
247                                 if( !X_Type_isNumber( obj.phase = _xnode = X_NodeAnime_detectWaitAnimation( xnode, obj.duration ) ) ){\r
248                                         _xnode[ '_anime' ].follower = true;\r
249                                         obj.phase = 3; // 後続待機\r
250                                 };\r
251                         } else {\r
252                                 obj.follower = false;\r
253                         };\r
254                 };\r
255         };\r
256         \r
257         for( ; i; ){\r
258                 rm    = false;\r
259                 xnode = list[ --i ];\r
260                 obj   = xnode[ '_anime' ];\r
261 \r
262                 switch( obj.phase ){\r
263                         case 7 : // アニメーション中\r
264                                 if( now < obj.destTime ){\r
265                                         easing = obj.progress  = obj.easing.fn( ( now - obj.startTime ) / obj.duration );\r
266                                         X_NodeAnime_updatePosition( xnode, \r
267                                                 obj.x = ( obj.destX - obj.startX ) * easing + obj.startX | 0,\r
268                                                 obj.y = ( obj.destY - obj.startY ) * easing + obj.startY | 0,\r
269                                                 obj.a = ( obj.destA - obj.startA ) * easing + obj.startA, true );\r
270                                         c = true;\r
271                                         break;\r
272                                 };\r
273                                 // アニメーション終了\r
274                         case 4 : // 強制停止(GPU転送予約)\r
275                                 lazy = !obj.follower && !obj.releaseNow && obj.lazyRelease;\r
276                                 X_NodeAnime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, !!lazy );\r
277 \r
278                                 xnode[ 'asyncDispatch' ]( X_EVENT_ANIME_END );\r
279                                 \r
280                                 if( lazy ){\r
281                                         console.log( 'アニメーション終了(' + obj.phase + ') -> GPU 解除待機 ' + lazy );\r
282                                         obj.releaseTime = now + lazy;\r
283                                         obj.phase = 5; // GPU解除待ち\r
284                                         c = true;\r
285                                 } else {\r
286                                         console.log( 'アニメーション終了(' + obj.phase + ') -> ' );\r
287                                         rm = true;\r
288                                 };\r
289                                 break;\r
290 \r
291                         case 6 : // アニメーション開始可能\r
292                                 obj.startTime = now;\r
293                                 obj.destTime  = now + obj.duration;\r
294                                 obj.phase     = 7; // アニメーション中\r
295                                 obj.progress  = 0;\r
296                                 X_NodeAnime_updatePosition( xnode, obj.startX, obj.startY, obj.startA, true );\r
297                                 xnode[ 'asyncDispatch' ]( X_EVENT_ANIME_START );\r
298                                 c = true;\r
299                                 break;\r
300                         \r
301                         case 5 : // GPU解除待ち\r
302                                 if( obj.releaseTime <= now || obj.follower || obj.releaseNow ){\r
303                                         X_NodeAnime_translateZ && X_NodeAnime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, false );\r
304                                         rm = true;\r
305                                 } else {\r
306                                         c = true;\r
307                                 };\r
308                                 break;  \r
309                 };\r
310                 \r
311                 obj.releaseNow = false;\r
312                 \r
313                 if( rm ){\r
314                         X_NodeAnime_translateZ && xnode[ 'asyncDispatch' ]( X_EVENT_GPU_RELEASED );\r
315                         // 後続のアニメーションがある場合\r
316                         if( obj.follower ) X_NodeAnime_needsDetection = c = true;\r
317                         list.splice( i, 1 );\r
318                         obj.phase = 0;\r
319                 };\r
320         };\r
321         \r
322         //c && console.log( 'anime... ' + X_Node_updateTimerID );\r
323         \r
324         if( X_NodeAnime_reserved = c ){\r
325                 if( X_Node_updateTimerID ){\r
326                         // scrollbox では X_System X_EVENT_UPDATED は不可。。。\r
327                         !e || e.type !== X_EVENT_UPDATED ?\r
328                                 X_System[ 'listen' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations ) :\r
329                                 X_NodeAnime_updateTimerID && X_Timer_cancelFrame( X_NodeAnime_updateTimerID );\r
330                         X_NodeAnime_updateTimerID = 0;\r
331                 } else {\r
332                         X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
333                         X_NodeAnime_updateTimerID = X_Timer_requestFrame( X_NodeAnime_updateAnimations );\r
334                 };\r
335         } else {\r
336                 X_System[ 'unlisten' ]( X_EVENT_UPDATED, X_NodeAnime_updateAnimations );\r
337                 X_NodeAnime_updateTimerID = 0;\r
338         };\r
339 };\r
340 \r
341 /*\r
342  * アニメーション開始、アニメーション中、強制停止(GPU転送予約)、GPU解除待ち の要素をリストの先頭に\r
343  */\r
344 function X_NodeAnime_sortAnimationNode( xnode1, xnode2 ){\r
345         var a = 4 <= xnode1[ '_anime' ].phase,\r
346                 b = 4 <= xnode2[ '_anime' ].phase;\r
347         \r
348     if( ( a && b ) && ( !a && !b ) ){ // Chrome のみ\r
349         return xnode1[ '_anime' ].uid - xnode2[ '_anime' ].uid;\r
350     };\r
351     return a ? -1 : 1;\r
352 };\r
353 \r
354 function X_NodeAnime_updatePosition( xnode, x, y, opacity, useGPU ){\r
355         //console.log( 'updatePosition x:' + x + ' gpu:' + !!useGPU );\r
356         if( X_NodeAnime_hasTransform ){\r
357                 xnode[ 'css' ]({\r
358                         transform : 'translate(' + ( x | 0 ) + 'px,' + ( y | 0 ) + 'px)' + ( useGPU ? X_NodeAnime_translateZ : '' ),\r
359                         opacity   : opacity\r
360                 });\r
361         } else {\r
362                 x === x && xnode[ 'css' ]( 'left', ( x | 0 ) + 'px' );\r
363                 y === y && xnode[ 'css' ]( 'top', ( y | 0 ) + 'px' );\r
364                 opacity === opacity && xnode[ 'css' ]( 'opacity', opacity );\r
365         };\r
366 \r
367         if( X_NodeAnime_translateZ ){\r
368                 if( useGPU ){\r
369                         if( xnode[ '_flags' ] & X_NodeFlags_GPU_RELEASE_RESERVED ){\r
370                                 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;\r
371                                 xnode[ '_flags' ] |= X_NodeFlags_GPU_NOW;\r
372                         } else\r
373                         if( !( xnode[ '_flags' ] & X_NodeFlags_GPU_NOW ) ){\r
374                                 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;\r
375                                 xnode[ '_flags' ] |= X_NodeFlags_GPU_RESERVED;\r
376                         };\r
377                 } else {\r
378                         if( xnode[ '_flags' ] & X_NodeFlags_GPU_NOW ){\r
379                                 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;\r
380                                 xnode[ '_flags' ] |= X_NodeFlags_GPU_RELEASE_RESERVED;\r
381                         } else\r
382                         if( xnode[ '_flags' ] & X_NodeFlags_GPU_RESERVED ){\r
383                                 xnode[ '_flags' ] &= X_Node_BitMask_RESET_GPU;\r
384                         };\r
385                 };              \r
386         };\r
387 };\r
388 \r
389 \r
390 var\r
391         X_NodeAnime_ease = {\r
392                 'quadratic' : {\r
393                         style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',\r
394                         fn: function (k) {\r
395                                 return k * ( 2 - k );\r
396                         }\r
397                 },\r
398                 'circular' : {\r
399                         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
400                         fn: function (k) {\r
401                                 return Math.sqrt( 1 - ( --k * k ) );\r
402                         }\r
403                 },\r
404                 'back' : {\r
405                         style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',\r
406                         fn: function (k) {\r
407                                 var b = 4;\r
408                                 return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;\r
409                         }\r
410                 },\r
411                 'bounce' : {\r
412                         style: '',\r
413                         fn: function (k) {\r
414                                 if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {\r
415                                         return 7.5625 * k * k;\r
416                                 } else if ( k < ( 2 / 2.75 ) ) {\r
417                                         return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;\r
418                                 } else if ( k < ( 2.5 / 2.75 ) ) {\r
419                                         return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;\r
420                                 } else {\r
421                                         return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;\r
422                                 }\r
423                         }\r
424                 },\r
425                 'elastic' : {\r
426                         style: '',\r
427                         fn: function (k) {\r
428                                 var f = 0.22,\r
429                                         e = 0.4;\r
430 \r
431                                 if ( k === 0 ) { return 0; }\r
432                                 if ( k == 1 ) { return 1; }\r
433 \r
434                                 return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );\r
435                         }\r
436                 }\r
437         };\r