OSDN Git Service

Version 0.6.110, Super & superCall.
[pettanr/clientJs.git] / 0.6.x / js / 02_dom / 10_XNodeAnime.js
1 \r
2 \r
3 var\r
4         ease = {\r
5                 quadratic: {\r
6                         style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',\r
7                         fn: function (k) {\r
8                                 return k * ( 2 - k );\r
9                         }\r
10                 },\r
11                 circular: {\r
12                         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
13                         fn: function (k) {\r
14                                 return Math.sqrt( 1 - ( --k * k ) );\r
15                         }\r
16                 },\r
17                 back: {\r
18                         style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',\r
19                         fn: function (k) {\r
20                                 var b = 4;\r
21                                 return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;\r
22                         }\r
23                 },\r
24                 bounce: {\r
25                         style: '',\r
26                         fn: function (k) {\r
27                                 if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {\r
28                                         return 7.5625 * k * k;\r
29                                 } else if ( k < ( 2 / 2.75 ) ) {\r
30                                         return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;\r
31                                 } else if ( k < ( 2.5 / 2.75 ) ) {\r
32                                         return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;\r
33                                 } else {\r
34                                         return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;\r
35                                 }\r
36                         }\r
37                 },\r
38                 elastic: {\r
39                         style: '',\r
40                         fn: function (k) {\r
41                                 var f = 0.22,\r
42                                         e = 0.4;\r
43 \r
44                                 if ( k === 0 ) { return 0; }\r
45                                 if ( k == 1 ) { return 1; }\r
46 \r
47                                 return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );\r
48                         }\r
49                 }\r
50         };\r
51                 \r
52 // 新規アニメーションが追加された場合、\r
53 // tree が dirty なら AFTER_COMMIT を待つ\r
54 // 1) xnode の既存アニメーションとの親子関係の調査\r
55 // 親なら -> 既存アニメーションの GPU レイヤー解除\r
56 // 子なら -> GPU レイヤーを設定しない\r
57 // 2) 目標座標のセット\r
58 // 3) アニメーション完了後も GPU レイヤーはしばらく解除しない スクロール等の連続アニメーション時に GPU 転送時間で画面ががたつくから\r
59 // アニメ中の remove\r
60 \r
61 var X_Node_ANIMATIONS            = [],\r
62         X_Node_Anime_updateTimerID   = 0,\r
63         X_Node_Anime_needsDetection  = false,\r
64         X_Node_Anime_hasTransform    = !!X_Node_CSS_VENDER_PREFIX[ 'transform' ],\r
65         /* Opera mobile で  translateZ(0) が有効だと XY が 0 0 になる */\r
66         X_Node_Anime_translateZ      = X_Node_CSS_VENDER_PREFIX[ 'perspective' ] && !X.UA.OperaMobile && !X.UA.OperaTablet ? ' translateZ(0)' : '',\r
67         X_Node_Anime_hasTransition   = !!X_Node_CSS_VENDER_PREFIX[ 'transitionDelay' ] && !X.UA.Opera, // Opera12(XP,8.1) 切った方がスムース\r
68         X_Node_Anime_transitionProps = X_Node_Anime_hasTransform ? X_Node_CSS_VENDER_PREFIX[ 'transform' ] : 'left,top';\r
69 \r
70 Node.prototype.animate = function( start, dest, duration, easing, wait ){\r
71         var obj = this._anime || ( this._anime = {} ), current;\r
72         \r
73         if( X_Node_Anime_hasTransition && this._rawObject ){\r
74                 current = {}; //X_Node_Anime_getComputedPosition( this );\r
75         };\r
76         \r
77         obj.duration  = X.Type.isFinite( duration ) && 0 <= duration ? duration : 500;\r
78         obj.easing    = ease[ easing ] || ease.circular;\r
79         // 現在 GPUレイヤーのtop になっているか?将来については phase で判定\r
80         obj.gpuParent = obj.gpuParent || false;\r
81         obj.phase     = duration === 0 ? 9 : 0; //\r
82         obj.wait      = X.Type.isFinite( wait ) ? wait : 1000;\r
83                 \r
84         obj.startTime = X_Timer_now();\r
85         obj.startX    = ( start.x || start.x === 0 ) ? start.x : obj.x || current && current.x || 0;\r
86         obj.startY    = ( start.y || start.y === 0 ) ? start.y : obj.y || current && current.y || 0;\r
87         obj.startA    = 0 <= start.opacity && start.opacity <= 1 ? start.opacity : obj.a || current && current.a || 1;\r
88         \r
89         obj.destTime  = obj.startTime + obj.duration;\r
90         obj.destX     = ( dest.x || dest.x === 0 ) ? dest.x : obj.destX || 0;\r
91         obj.destY     = ( dest.y || dest.y === 0 ) ? dest.y : obj.destY || 0;\r
92         obj.destA     = 0 <= dest.opacity && dest.opacity <= 1 ? dest.opacity : obj.destA || 1;\r
93 \r
94         \r
95         if( obj.gpuTimerID ){\r
96                 X.Timer.remove( obj.gpuTimerID );\r
97                 delete obj.gpuTimerID;\r
98         };\r
99         \r
100         X_Node_Anime_needsDetection = true;\r
101         if( X_Node_Anime_hasTransition ){\r
102                 X_Node_Anime_reserveUpdate();\r
103         } else {\r
104                 X_Node_Anime_updateTimerID || ( X_Node_Anime_updateTimerID = X.Timer.requestFrame( X_Node_Anime_updateAnimationsNoTransition ) );               \r
105         };\r
106 \r
107         X_Node_ANIMATIONS.indexOf( this ) === -1 &&\r
108                 ( X_Node_ANIMATIONS[ X_Node_ANIMATIONS.length ] = this );\r
109         \r
110         //console.log( 'animate ' + this._id + ' y:' + obj.startY + ' > ' + obj.destY + ' d:' + obj.duration );\r
111         \r
112         return this;\r
113 };\r
114 \r
115 Node.prototype.stop = function(){\r
116         var obj = this._anime;\r
117         if( !obj ) return;\r
118         if( X_Node_Anime_hasTransition ){\r
119                 obj.phase = 100;\r
120                 X_Node_Anime_needsDetection = true;\r
121                 X_Node_Anime_reserveUpdate();\r
122         } else {\r
123                 X_Node_ANIMATIONS.splice( X_Node_ANIMATIONS.indexOf( this ), 1 );\r
124                 obj.gpuTimerID && X.Timer.remove( obj.gpuTimerID );\r
125                 delete this._anime;             \r
126         };\r
127         return this;\r
128 };\r
129 \r
130 function X_Node_Anime_reserveUpdate(){\r
131         if( !X_Node_Anime_updateTimerID ){\r
132                 // Opera12 requestAnimationFrame では transition が動かない、、、\r
133                 X_Node_Anime_updateTimerID =\r
134                         X_UA.Opera ?\r
135                                 X.Timer.once( 0, X_Node_Anime_updateAnimations ) :\r
136                                 X.Timer.requestFrame( X_Node_Anime_updateAnimations );\r
137         };\r
138 };\r
139 \r
140 function X_Node_Anime_updateAnimations(){\r
141         var i = X_Node_ANIMATIONS.length, ret, c = false;\r
142         \r
143         //console.log( 'updateAnimations ' + i + ' ' + X_Node_Anime_needsDetection );\r
144         \r
145         if( X_Node_Anime_needsDetection ) X_Node_Anime_detectAnimationLayers();\r
146         \r
147         for( ; i; ){\r
148                 xnode = X_Node_ANIMATIONS[ --i ];\r
149                 //console.log( 'phase : ' + xnode._id + ' ' + xnode._anime.phase + ' ' + xnode._anime.duration );\r
150                 ret = X_Node_Anime_updateAnimation( xnode );\r
151                 if( ret === true ){\r
152                         X_Node_ANIMATIONS.splice( i, 1 );\r
153                         xnode._anime.gpuTimerID && X.Timer.remove( xnode._anime.gpuTimerID );\r
154                         delete xnode._anime;\r
155                 } else\r
156                 if( ret !== false ){\r
157                         c = true;\r
158                 };\r
159         };\r
160         \r
161         X_Node_Anime_updateTimerID = 0;\r
162         if( c ){\r
163                 X_Node_Anime_reserveUpdate();\r
164         };\r
165 };\r
166 \r
167 // TODO X.Timer.requestFrame 経由の BEFORE_UPDATE で更新を行う\r
168 function X_Node_Anime_detectAnimationLayers(){\r
169         var i = X_Node_ANIMATIONS.length,\r
170                 l = i,\r
171                 j, xnode, parent, hasGPUChild, changed, remove;\r
172 \r
173         for( ; i; ){\r
174                 xnode = X_Node_ANIMATIONS[ --i ];\r
175                 parent = hasGPUChild = false;\r
176                 //console.log( 'koko- ' + xnode._id + ' ' + xnode._anime.phase );\r
177                 for( j = l; j; ){\r
178                         _xnode = X_Node_ANIMATIONS[ --j ];\r
179                         \r
180                         if( xnode.parent === _xnode.parent ){\r
181                                 //console.log( 'cont ' + xnode._anime.phase );\r
182                                 continue;\r
183                         } else\r
184                         if( _xnode.contains( xnode ) ){\r
185                                 if( _xnode._anime.phase === 3 || _xnode._anime.phase === 10 ){\r
186                                         _xnode._anime.phase = 15;\r
187                                 } else\r
188                                 if( xnode._anime.gpuParent ){\r
189                                         changed = parent = true;\r
190                                         xnode._anime.phase = xnode._anime.phase === 2 ? 6 : 15;// GPU レイヤーの解除 > アニメーションは継続, すでに終了フェイズなら破棄\r
191                                 } else\r
192                                 if( [ 7, 8, 9, 13, 14 ].indexOf( xnode._anime.phase ) !== -1 ){// GPU レイヤーの中止\r
193                                         changed = parent = true;\r
194                                         xnode._anime.phase -= 8;\r
195                                 };\r
196                                 break;\r
197                         } else\r
198                         if( xnode.contains( _xnode ) ){\r
199                                 if( xnode._anime.phase === 3 || xnode._anime.phase === 10 ){\r
200                                         xnode._anime.phase = 15;\r
201                                 } else\r
202                                 if( _xnode._anime.gpuParent ){\r
203                                         changed = hasGPUChild = true;\r
204                                         _xnode._anime.phase = _xnode._anime.phase === 2 ? 6 : 15;// GPU レイヤーの解除 > アニメーションは継続, すでに終了フェイズなら破棄\r
205                                 } else\r
206                                 if( [ 7, 8, 9, 13, 14 ].indexOf( _xnode._anime.phase ) !== -1 ){// GPU レイヤーの中止\r
207                                         changed = hasGPUChild = true;\r
208                                         _xnode._anime.phase -= 8;\r
209                                 };\r
210                                 break;\r
211                         };\r
212                 };\r
213                 if( !parent && xnode._anime.phase !== 15 ){\r
214                         if( xnode._anime.phase === 0 ){\r
215                                 // 新規\r
216                                 changed = changed || !xnode._anime.gpuParent;\r
217                                 xnode._anime.phase = hasGPUChild ? 7 : 8;// 非GPU -> GPU 子に GPU アニメをもつなら、タイミングをずらす。\r
218                         } else\r
219                         if( [ 3, 4, 10, 100 ].indexOf( xnode._anime.phase ) === -1 ){\r
220                                 // 非GPU -> GPU\r
221                                 changed = changed || !xnode._anime.gpuParent;\r
222                                 //console.log( 'koko? ' + xnode._anime.phase );\r
223                                 xnode._anime.phase = hasGPUChild ? 13 : 14;// 非GPU -> GPU 子に GPU アニメをもつなら、タイミングをずらす。\r
224                         };\r
225                 };\r
226         };\r
227         \r
228         X_Node_Anime_needsDetection = false;\r
229 };\r
230 \r
231 function X_Node_Anime_updateAnimation( xnode ){\r
232         var obj   = xnode._anime,\r
233                 phase = obj.phase,\r
234                 current, time;\r
235         switch( phase ){\r
236                 case -1 :// 子の GPU レイヤー解除待ち\r
237                 case  7 :\r
238                         ++obj.phase;\r
239                         break;\r
240                 case  0 : // 開始位置+アニメーションの設定 \r
241                 case  8 :\r
242                         X_Node_Anime_updatePosition( xnode, obj.startX, obj.startY, obj.startA, phase === 8 );\r
243                         ++obj.phase;\r
244                         break;\r
245                 case  1 :\r
246                 case  9 : // 終了位置の設定\r
247                         obj.gpuParent = phase === 9;\r
248                         X_Node_Anime_updateTransition( xnode, obj.duration, obj.easing );\r
249                         X_Node_Anime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, obj.gpuParent );\r
250                         obj.phase = 2;\r
251                         break;\r
252                 \r
253                 case 2 :\r
254                         // アニメーション中\r
255                         return false;\r
256                 \r
257                 case 3 : // アニメーションの解除\r
258                         X_Node_Anime_updateTransition( xnode, 0 );\r
259                         obj.phase = obj.gpuParent ? 10 : 4;\r
260                         break;\r
261 \r
262                 case 4 :\r
263                         // アニメーションは停止・GPU = false -> リストから削除\r
264                         obj.gpuParent = false;\r
265                         return true;\r
266 \r
267                 case 10 :\r
268                         // アニメーションは停止・GPUレイヤーは解除していない(再アニメーションに備えて待機)\r
269                         if( !obj.gpuTimerID ){\r
270                                 obj.gpuTimerID = X.Timer.once( obj.wait, xnode, X_Node_Anime_releaseGPULayer );\r
271                         };\r
272                         return false;\r
273                 \r
274                 case  5 :\r
275                 case 13 :\r
276                         // 子のGPU解除待ち\r
277                         ++obj.phase;\r
278                         break;\r
279                 \r
280                 // GPU レイヤーの変更> アニメーションは継続,但し残り時間が短ければ停止\r
281                 case  6 :               \r
282                 case 14 :\r
283                         now  = X_Timer_now();\r
284                         time = obj.duration - now + obj.startTime;\r
285                         if( time < 16 ){\r
286                                 X_Node_Anime_updateTransition( xnode, 0 );\r
287                                 X_Node_Anime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, phase === 14 );\r
288                                 obj.phase = phase === 14 ? 10 : 4;\r
289                         } else {\r
290                                 current = X_Node_Anime_getComputedPosition( xnode );\r
291                                 obj.startX    = current.x;\r
292                                 obj.startY    = current.y;\r
293                                 obj.startA    = current.a;\r
294                                 obj.duration  = time;\r
295                                 obj.startTime = now;\r
296                                 //X_Node_Anime_updateTransition( xnode, time, obj.easing );\r
297                                 X_Node_Anime_updatePosition( xnode, current.x, current.y, current.a, phase === 14 );\r
298                                 obj.phase = phase === 14 ? 9 : 1;\r
299                         };\r
300                         break;\r
301                 \r
302                 case 15 :\r
303                         // GPU有効で停止(待機)している xnode の解除\r
304                         console.log( 'GPU有効で停止(待機)している xnode の解除' + xnode._tag + xnode.getOrder() );\r
305                         X_Node_Anime_updateTransition( xnode, 0 );\r
306                         X_Node_Anime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, false );\r
307                         obj.gpuTimerID && X.Timer.remove( obj.gpuTimerID );\r
308                         return true;\r
309                 \r
310                 case 100 : // stop() : アニメーションの中断して削除\r
311                         console.log( 'stop() gpu:' + obj.gpuParent );\r
312                         current = X_Node_Anime_getComputedPosition( xnode );\r
313                                                 \r
314                         X_Node_Anime_updateTransition( xnode, 0 );\r
315                         X_Node_Anime_updatePosition( xnode, current.x, current.y, current.a, obj.gpuParent );\r
316                         obj.phase = obj.gpuParent ? 10 : 4;\r
317                         break;\r
318                 \r
319         };\r
320 };\r
321 \r
322 function X_Node_Anime_getComputedPosition( that ) {\r
323         var matrix = X_node_CSS_getComputedStyle( that._rawObject, null ),\r
324                 x, y;\r
325 \r
326         if ( X_Node_Anime_hasTransform ) {\r
327                 matrix = matrix[ X_Node_CSS_VENDER_PREFIX[ 'transform' ] ].split( ')' )[ 0 ].split( ', ' );\r
328                 x = + ( matrix[ 12 ] || matrix[ 4 ] );\r
329                 y = + ( matrix[ 13 ] || matrix[ 5 ] );\r
330         } else {\r
331                 x = + parseInt( matrix.left );\r
332                 y = + parseInt( matrix.top );\r
333         };\r
334         \r
335         return { x: x, y: y, a : matrix[ X_Node_CSS_Support[ 'opacity' ] ] };\r
336 };\r
337 \r
338 function X_Node_Anime_onTransitionEnd(){\r
339         if( this._anime.phase !== 2 ) return X.Callback.PREVENT_DEFAULT;\r
340         this._anime.phase = 3;\r
341         X_Node_Anime_needsDetection = true;\r
342         X_Node_Anime_reserveUpdate();\r
343         console.log( 'トランジション終了' );\r
344         return X.Callback.UN_LISTEN | X.Callback.PREVENT_DEFAULT;\r
345 };\r
346 \r
347 function X_Node_Anime_releaseGPULayer(){\r
348         var obj = this._anime;\r
349         X_Node_Anime_updatePosition( this, obj.destX, obj.destY, obj.destA, false );\r
350         X_Node_ANIMATIONS.splice( X_Node_ANIMATIONS.indexOf( this ), 1 );\r
351         delete obj.gpuTimerID;\r
352         delete this._anime;\r
353         console.log( 'GPUレイヤーの破棄' );\r
354 };\r
355 \r
356 function X_Node_Anime_updateTransition( xnode, time, easing ){\r
357         // 開始座標のセット(新規のみ)\r
358         // アニメーション指定のセット(または解除)(対象のみ)\r
359         // 目標座標のセット\r
360         if( time ){\r
361                 xnode.listenOnce( 'transitionend', X_Node_Anime_onTransitionEnd );\r
362         } else {\r
363                 xnode.unlisten( 'transitionend', X_Node_Anime_onTransitionEnd );\r
364         };\r
365         xnode.css({\r
366                 transitionProperty       : X_Node_Anime_transitionProps + ',opacity',\r
367                 willChange               : time ? X_Node_Anime_transitionProps + ',opacity' : '',\r
368                 backfaceVisibility       : time ? 'hidden' : '',\r
369                 transitionTimingFunction : time ? easing.style : '',\r
370                 transitionDelay          : '0s',\r
371                 transitionDuration       : time ? time + 'ms' : ''\r
372         });\r
373 };\r
374 \r
375 function X_Node_Anime_updatePosition( xnode, x, y, opacity, useGPU ){\r
376         if( X_Node_Anime_hasTransform ){\r
377                 xnode.css({\r
378                         transform : 'translate(' + ( x | 0 ) + 'px,' + ( y | 0 ) + 'px)' + ( useGPU ? X_Node_Anime_translateZ : '' ),\r
379                         opacity   : opacity\r
380                 });\r
381         } else {\r
382                 xnode.css({\r
383                         left    : ( x | 0 ) + 'px',\r
384                         top     : ( y | 0 ) + 'px',\r
385                         opacity : opacity\r
386                 });\r
387         };\r
388         // xnode._anime.x, y\r
389 };\r
390 \r
391 function X_Node_Anime_updateAnimationsNoTransition(){\r
392         var i = X_Node_ANIMATIONS.length,\r
393                 now = X_Timer_now(),\r
394                 obj,\r
395                 newX, newY, newA, easing,\r
396                 c = false;\r
397         \r
398         for( ; i; ){\r
399                 xnode = X_Node_ANIMATIONS[ --i ];\r
400                 obj   = xnode._anime;\r
401 \r
402                 if( obj.destTime <= now ){\r
403                         X_Node_Anime_updatePosition( xnode, obj.destX, obj.destY, obj.destA, false );\r
404                         xnode.asyncDispatch( 'transitionend' );\r
405                         \r
406                         X_Node_ANIMATIONS.splice( i, 1 );\r
407                         delete xnode._anime;                    \r
408                 } else {\r
409                         easing = obj.easing.fn( ( now - obj.startTime ) / obj.duration );\r
410                         newX   = ( obj.destX - obj.startX ) * easing + obj.startX;\r
411                         newY   = ( obj.destY - obj.startY ) * easing + obj.startY;\r
412                         newA   = ( obj.destA - obj.startA ) * easing + obj.startA;\r
413                         X_Node_Anime_updatePosition( xnode, newX, newY, newA, false );\r
414                         obj.x = newX;\r
415                         obj.y = newY;\r
416                         obj.a = newA;\r
417                         c = true;       \r
418                 };\r
419         };\r
420         \r
421         if( c ){\r
422                 X_Node_Anime_updateTimerID = X.Timer.requestFrame( X_Node_Anime_updateAnimationsNoTransition );\r
423         } else {\r
424                 X_Node_Anime_updateTimerID = 0;\r
425         };\r
426 };\r
427 \r