OSDN Git Service

Version 0.6.12.
[pettanr/clientJs.git] / 0.6.x / js / dom / 15_XDomAnime.js
1 /*\r
2  * GPU レイヤーを使うとスムースなアニメーションが得られる\r
3  * 但し、古めのデバイスでは処理落ちが起こる\r
4  * 処理落ちは、最も古い初代 iPod touch (iOS3) では特に顕著に発生するため、テスト機としてちょうどいい\r
5  * css の書き方によっても、処理落ちやガタツキに影響するので、デザイナーが気軽に触れるものではない\r
6  * \r
7  * transform の登場によって、要素の表示位置を変更する方法が、css-p との2択になった。\r
8  * 初代 iPod touch では、簡単な css-p すら無視してくれるので、積極的に transform を使っていけ、ということだと思う。\r
9  * 但し transform には様々な癖があり、一筋縄ではいかない。\r
10  * \r
11  * DHTML を扱う専用モジュールを用意して、そこにコードとノウハウを集積する。\r
12  * \r
13  * GPUレイヤーを使用している場合\r
14  * 新しいアニメーション・キューが追加されたときに、現在アニメーションしている要素と親子関係ならば、以前のアニメーションは直ちに停止し、GPUレイヤーを解除。\r
15  */\r
16 \r
17 X.Dom.Anime = (function( window, document, navigator, Math, undefined ){\r
18 \r
19 var QUEUE_LIST = [],\r
20         timerID,\r
21         ABS             = new Function( 'v', 'return v<0?-v:v' ),\r
22         animeEnabled    = !X.UA.EInk,\r
23         vendor          = false,\r
24         cssVendor       = '',\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
30 \r
31 /*\r
32  * 新規アニメーション要素が現在アニメーションしている要素と親子関係の場合、\r
33  * 親子関係のアニメーションを停止して一挙に最後の状態へ\r
34  */\r
35 function registerQueue( queue ){\r
36         var list  = QUEUE_LIST,\r
37                 l     = list.length,\r
38                 xnode = queue.xnode,\r
39                 q, _xnode;\r
40         for( ; l; ){\r
41                 q = list[ --l ];\r
42                 _xnode = q.xnode;\r
43                 _xnode !== xnode && ( _xnode.contains( xnode ) || xnode.contains( _xnode ) ) && q.stop( true );\r
44         };\r
45         list[ list.length ] = queue;\r
46         !timerID && ( timerID = X.Timer.nextFrame( onEnterFrame ) );\r
47 };\r
48 \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
55         } else {\r
56                 QUEUE_LIST.splice( i, 1 );\r
57         };\r
58 };\r
59 \r
60 function onEnterFrame(){\r
61         var list = QUEUE_LIST,\r
62                 i    = 0,\r
63                 l    = list.length,\r
64                 t    = X.getTime();\r
65         for( ; l; ){\r
66                 list[ --l ].tick( t );\r
67         };\r
68         list.length && ( timerID = X.Timer.nextFrame( onEnterFrame ) );\r
69 };\r
70 \r
71 var AnimationQueue = function( xnode ){\r
72         this.xnode = xnode;\r
73 };\r
74 \r
75 X.Class._override( AnimationQueue.prototype,\r
76 {\r
77         state     : 0, // 0:停止, 1:登録済, 2:アニメ中の親子要素のGPUレイヤー解除待ち, 3:アニメ中;GPUレイヤーにセット、目標値のセット(transisiton有効), 4:\r
78         xnode     : null,\r
79         startTime : 0,\r
80         type      : 0,\r
81         list      : null,\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
88                                         prop = list[ i ];\r
89                                         break;\r
90                                 };\r
91                         };\r
92                         if( !prop ){\r
93                                 prop = {};\r
94                                 switch( type ){\r
95                                         case 'width' :\r
96                                                 prop[ 2 ] = this.xnode.width(); // currentValue\r
97                                                 break;\r
98                                         case 'height' :\r
99                                                 prop[ 2 ] = this.xnode.height(); // currentValue\r
100                                                 break;\r
101                                         case 'x' :\r
102                                         case 'left' :\r
103                                                 type = transform ? 'translateX' : 'left';\r
104                                                 prop[ 2 ] = this.xnode.x(); // currentValue\r
105                                                 break;\r
106                                         case 'y' :\r
107                                         case 'top' :\r
108                                                 type = transform ? 'translateY' : 'top';\r
109                                                 prop[ 2 ] = this.xnode.y(); // currentValue\r
110                                                 break;\r
111                                 };\r
112                                 prop[ 0 ] = type;\r
113                         };\r
114                         prop[ 1 ] = data[ type ]; // target value;\r
115                         prop[ 3 ] = prop[ 1 ] - prop[ 2 ];\r
116                 };\r
117 \r
118                 this.state === 0 && registerQueue( this );\r
119 \r
120                 this.state     = 1;\r
121                 this.startTime = X.getTime();\r
122                 this.duration  = duration;\r
123                 if( transition ){\r
124                         this.timming = 'liner';//timmingFunction;\r
125                 } else {\r
126                         this.timming = Liner;\r
127                 };\r
128                 \r
129         },\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
132                         return;\r
133                 };\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
137         },\r
138         tick :\r
139                 transition ?\r
140                         (function( time ){\r
141                                 var ratio  = ( time - this.startTime ) / this.duration,\r
142                                         list   = this.list,\r
143                                         xnode  = this.xnode,\r
144                                         css;\r
145                                 if( this.state === 1 ){\r
146                                         this.state = 2;\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
153                                         xnode.css( css );\r
154                                         delete this._transProp;                 \r
155                                 } else\r
156                                 if( this.state === 2 ){\r
157                                         this.state = 3;\r
158                                         css = {};\r
159                                         css[ transform ] = this._transform + translateZ;\r
160                                         xnode.css( css )\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
164                                 } else\r
165                                 if( 1 <= ratio ){\r
166                                         css = {};\r
167                                         css[ transform ] = this._transform; // GPU support off\r
168                                         delete this._transform;\r
169                                         list.length = 0;\r
170                                         xnode.css( css )\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
174                                 } else {\r
175                                         xnode.dispatch( { type : X.UI.Event.ANIME, ratio : rario, target : xnode } );\r
176                                 };\r
177                         }) :\r
178                         (function( time ){\r
179                                 var ratio  = ( time - this.startTime ) / this.duration,\r
180                                         list   = this.list,\r
181                                         xnode  = this.xnode,\r
182                                         css;\r
183                                 if( 1 <= ratio ){\r
184                                         this.state = 0;\r
185                                         calculateCSS( css = {}, list, 1, time - this.startTime, this.duration );\r
186                                         list.length = 0;\r
187                                         xnode.css( css )\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
190                                 } else {\r
191                                         this.state = 3;\r
192                                         calculateCSS( css = {}, list, ratio, time - this.startTime, this.duration );\r
193                                         xnode.css( css )\r
194                                                 .dispatch( { type : X.UI.Event.ANIME, ratio : rario, target : xnode } );\r
195                                 };\r
196                         }),\r
197         onTransitionEnd : transition && (function(){\r
198                 return this.dispatch( { type : X.UI.Event.ANIME_END, ratio : 1, target : xnode } );\r
199         }),\r
200         /*\r
201          * transisitonProperty を集める\r
202          * trasform を集める\r
203          */\r
204         calculateCSS :\r
205                 transition ?\r
206                         (function( css, list ){\r
207                                 var i    = 0,\r
208                                         l    = list.length,\r
209                                         prop = [],\r
210                                         data, type, transX, transY, _transX, _transY;\r
211                                 for( ; i < l; ++i ){\r
212                                         data = list[ i ];\r
213                                         type = data[ 0 ];\r
214                                         switch( type ){\r
215                                                 case 'translateX' :\r
216                                                         transX  = data[ 2 ];\r
217                                                         _transX = data[ 1 ];\r
218                                                         break;\r
219                                                 case 'translateY' :\r
220                                                         transY  = data[ 2 ];\r
221                                                         _transY = data[ 1 ];\r
222                                                         break;\r
223                                                 default :\r
224                                                         css[ type ] = data[ 1 ];\r
225                                                         prop[ prop.length ] = type;\r
226                                         };\r
227                                 };\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
233                                                 \r
234                                         } else\r
235                                         if( transX ){\r
236                                                 css[ transform ] = cssVendor + 'translateX(' + transX + ')' + translateZ;\r
237                                                 this._transform  = cssVendor + 'translateX(' + _transX + ')';\r
238                                         } else\r
239                                         if( transY ){\r
240                                                 css[ transform ] = cssVendor + 'translateY(' + transY + ')' + translateZ;\r
241                                                 this._transform  = cssVendor + 'translateY(' + _transY + ')';\r
242                                         };                                      \r
243                                 };\r
244                                 this._transProp = prop.join( ',' );\r
245                         }) :\r
246                 transform ?\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
252                                         data = list[ i ];\r
253                                         type = data[ 0 ];\r
254                                         switch( type ){\r
255                                                 case 'translateX' :\r
256                                                         transX  = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
257                                                         break;\r
258                                                 case 'translateY' :\r
259                                                         transY  = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
260                                                         break;\r
261                                                 default :\r
262                                                         css[ type ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
263                                         };\r
264                                 };\r
265                                 if( transX && transY ){\r
266                                         this._transform  = cssVendor + 'translate(' + transX  + ',' + transY  + ')';\r
267                                         css[ transform ] = this._transform + translateZ;\r
268                                 } else\r
269                                 if( transX ){\r
270                                         this._transform  = cssVendor + 'translateX(' + transX + ')';\r
271                                         css[ transform ] = this._transform + translateZ;\r
272                                 } else\r
273                                 if( transY ){\r
274                                         this._transform  = cssVendor + 'translateY(' + transY + ')';\r
275                                         css[ transform ] = this._transform + translateZ;\r
276                                 };\r
277                         }) :\r
278                         (function( css, list, l, ratio, time, duration ){\r
279                                 var data,\r
280                                         easing = this.timming;\r
281                                 for( ; l; ){\r
282                                         data = list[ --l ];\r
283                                         css[ data[ 0 ] ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
284                                 };\r
285                         })\r
286 });\r
287 \r
288 function Liner( t, b, c, d ){ \r
289   return c * t / d + b;\r
290 };\r
291 \r
292 // original :\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
296 \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
303                 t2 = t2 - x2 / d2;\r
304         };\r
305         if (x < t0) return CubicBezier.curveY(y1, y2, t0);\r
306         if (x > t1) return CubicBezier.curveY(y1, y2, t1);\r
307         \r
308         t2 = x;\r
309         \r
310         // Fallback to the bisection method for reliability.\r
311         while (t0 < t1){\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
315                 else t1 = t2;\r
316                 t2 = (t1 - t0) * .5 + t0;\r
317         };\r
318         // Failure\r
319         return CubicBezier.curveY(y1, y2, t2);\r
320 \r
321 };\r
322 CubicBezier.curveX = function( x1, x2, t ){\r
323         var v = 1 - t;\r
324         return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;\r
325 };\r
326 CubicBezier.curveY = function( y1, y2, t ){\r
327         var v = 1 - t;\r
328         return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;\r
329 };\r
330 CubicBezier.derivativeCurveX = function( x1, x2, t ){\r
331         var v = 1 - t;\r
332         return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2;\r
333 };\r
334 \r
335 \r
336 \r
337 // export\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
341         return this;\r
342 };\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
346         return this;\r
347 };\r
348 \r
349 return {\r
350         X       :  1,\r
351         Y       :  2,\r
352         MOVE    :  4,\r
353         WIDTH   :  8,\r
354         HEIGHT  : 16,\r
355         SIZE    : 32,\r
356         OPACITY : 64\r
357 };\r
358         \r
359 })( window, document, navigator, Math );\r