OSDN Git Service

Version 0.6.25, bugfix.
[pettanr/clientJs.git] / 0.6.x / js / dom / 16_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         // X.Dom.listenOnce( X.Dom.Event.DOM_PRE_INIT 以降にアクセス可能\r
26     has3d           = X.Dom.Style.has3d,\r
27         transform       = X.Dom.Style.transform,\r
28         transformOrigin = X.Dom.Style.transformOrigin,\r
29         translateZ      = X.Dom.Style.translateZ,\r
30         transition      = X.Dom.Style.transition;\r
31 \r
32 /*\r
33  * 新規アニメーション要素が現在アニメーションしている要素と親子関係の場合、\r
34  * 親子関係のアニメーションを停止して一挙に最後の状態へ\r
35  */\r
36 function registerQueue( queue ){\r
37         var list  = QUEUE_LIST,\r
38                 l     = list.length,\r
39                 xnode = queue.xnode,\r
40                 q, _xnode;\r
41         for( ; l; ){\r
42                 q = list[ --l ];\r
43                 _xnode = q.xnode;\r
44                 _xnode !== xnode && ( _xnode.contains( xnode ) || xnode.contains( _xnode ) ) && q.stop( true );\r
45         };\r
46         list[ list.length ] = queue;\r
47         !timerID && ( timerID = X.Timer.nextFrame( onEnterFrame ) );\r
48 };\r
49 \r
50 function unregisterQueue( queue ){\r
51         var i = QUEUE_LIST.indexOf( queue );\r
52         if( i === -1 ) return;\r
53         if( QUEUE_LIST.length === 1 ){\r
54                 QUEUE_LIST.length = 0;\r
55                 timerID && X.Timer.cancelFrame( onEnterFrame );\r
56         } else {\r
57                 QUEUE_LIST.splice( i, 1 );\r
58         };\r
59 };\r
60 \r
61 function onEnterFrame(){\r
62         var list = QUEUE_LIST,\r
63                 i    = 0,\r
64                 l    = list.length,\r
65                 t    = X.getTime();\r
66         for( ; l; ){\r
67                 list[ --l ].tick( t );\r
68         };\r
69         list.length && ( timerID = X.Timer.nextFrame( onEnterFrame ) );\r
70 };\r
71 \r
72 var AnimationQueue = function( xnode ){\r
73         this.xnode = xnode;\r
74 };\r
75 \r
76 X.Class._override( AnimationQueue.prototype,\r
77 {\r
78         state     : 0, // 0:停止, 1:登録済, 2:アニメ中の親子要素のGPUレイヤー解除待ち, 3:アニメ中;GPUレイヤーにセット、目標値のセット(transisiton有効), 4:\r
79         xnode     : null,\r
80         startTime : 0,\r
81         type      : 0,\r
82         list      : null,\r
83         init : function( data, duration, timmingFunction ){\r
84                 var list, type, prop, data, target, i, l;\r
85                 if( !( list = this.list ) ) this.list = list = [];\r
86                 for( type in data ){\r
87                         for( i = 0, l = list.length; i < l; ++i ){\r
88                                 if( list[ i ][ 0 ] === type ){\r
89                                         prop = list[ i ];\r
90                                         break;\r
91                                 };\r
92                         };\r
93                         if( !prop ){\r
94                                 prop = {};\r
95                                 switch( type ){\r
96                                         case 'width' :\r
97                                                 prop[ 2 ] = this.xnode.width(); // currentValue\r
98                                                 break;\r
99                                         case 'height' :\r
100                                                 prop[ 2 ] = this.xnode.height(); // currentValue\r
101                                                 break;\r
102                                         case 'x' :\r
103                                         case 'left' :\r
104                                                 type = transform ? 'translateX' : 'left';\r
105                                                 prop[ 2 ] = this.xnode.x(); // currentValue\r
106                                                 break;\r
107                                         case 'y' :\r
108                                         case 'top' :\r
109                                                 type = transform ? 'translateY' : 'top';\r
110                                                 prop[ 2 ] = this.xnode.y(); // currentValue\r
111                                                 break;\r
112                                 };\r
113                                 prop[ 0 ] = type;\r
114                         };\r
115                         prop[ 1 ] = data[ type ]; // target value;\r
116                         prop[ 3 ] = prop[ 1 ] - prop[ 2 ];\r
117                 };\r
118 \r
119                 this.state === 0 && registerQueue( this );\r
120 \r
121                 this.state     = 1;\r
122                 this.startTime = X.getTime();\r
123                 this.duration  = duration;\r
124                 if( transition ){\r
125                         this.timming = 'liner';//timmingFunction;\r
126                 } else {\r
127                         this.timming = Liner;\r
128                 };\r
129                 \r
130         },\r
131         stop : function( opt_goEndPosition ){\r
132                 if( this.xnode.dispatch( { type : X.Dom.Event.ANIME_BEFORE_STOP, target : this.xnode } ) & X.Callback.PREVENT_DEFAULT ){\r
133                         return;\r
134                 };\r
135                 opt_goEndPosition && this.tick( this.startTime + this.duration );\r
136                 unregisterQueue( this );\r
137                 this.xnode.dispatch( { type : X.Dom.Event.ANIME_STOP, target : this.xnode } );  \r
138         },\r
139         tick :\r
140                 transition ?\r
141                         (function( time ){\r
142                                 var ratio  = ( time - this.startTime ) / this.duration,\r
143                                         list   = this.list,\r
144                                         xnode  = this.xnode,\r
145                                         css;\r
146                                 if( this.state === 1 ){\r
147                                         this.state = 2;\r
148                                         calculateCSS( css = {}, list );\r
149                                         css[ transformOrigin           ] = '0 0';\r
150                                         css[ transition.Property       ] = this._transProp;\r
151                                         css[ transition.Delay          ] = '0';\r
152                                         css[ transition.Duration       ] = this.duration + 'ms';\r
153                                         css[ transition.TimingFunction ] = this.timming;\r
154                                         xnode.css( css );\r
155                                         delete this._transProp;                 \r
156                                 } else\r
157                                 if( this.state === 2 ){\r
158                                         this.state = 3;\r
159                                         css = {};\r
160                                         css[ transform ] = this._transform + translateZ;\r
161                                         xnode.css( css )\r
162                                                 .listenOnce( transition.End, xnode, this.onTransitionEnd )\r
163                                                 .dispatch( { type : X.Dom.Event.ANIME_START, ratio : 1, target : xnode } );\r
164                                         this.startTime = time;\r
165                                 } else\r
166                                 if( 1 <= ratio ){\r
167                                         css = {};\r
168                                         css[ transform ] = this._transform; // GPU support off\r
169                                         delete this._transform;\r
170                                         list.length = 0;\r
171                                         xnode.css( css )\r
172                                                 .unlisten( transition.End, xnode, this.onTransitionEnd );\r
173                                 } else {\r
174                                         xnode.dispatch( { type : X.Dom.Event.ANIME, ratio : rario, target : xnode } );\r
175                                 };\r
176                         }) :\r
177                         (function( time ){\r
178                                 var offset = time - this.startTime,\r
179                                         ratio  = offset / 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, offset, this.duration );\r
186                                         list.length = 0;\r
187                                         xnode.css( css )\r
188                                                 .dispatch( { type : X.Dom.Event.ANIME, ratio : 1, target : xnode } );\r
189                                         xnode.dispatch( { type : X.Dom.Event.ANIME_END, ratio : 1, target : xnode } );\r
190                                 } else {\r
191                                         this.state = 3;\r
192                                         calculateCSS( css = {}, list, ratio, offset, this.duration );\r
193                                         xnode.css( css )\r
194                                                 .dispatch( { type : X.Dom.Event.ANIME, ratio : rario, target : xnode } );\r
195                                 };\r
196                         }),\r
197         onTransitionEnd : transition && (function(){\r
198                 this.dispatch( { type : X.Dom.Event.ANIME, ratio : 1, target : this.xnode } );\r
199                 return this.dispatch( { type : X.Dom.Event.ANIME_END, ratio : 1, target : this.xnode } );\r
200         }),\r
201         /*\r
202          * transisitonProperty を集める\r
203          * trasform を集める\r
204          */\r
205         calculateCSS :\r
206                 transition ?\r
207                         (function( css, list ){\r
208                                 var i    = 0,\r
209                                         l    = list.length,\r
210                                         prop = [],\r
211                                         data, type, transX, transY, _transX, _transY;\r
212                                 for( ; i < l; ++i ){\r
213                                         data = list[ i ];\r
214                                         type = data[ 0 ];\r
215                                         switch( type ){\r
216                                                 case 'translateX' :\r
217                                                         transX  = data[ 2 ];\r
218                                                         _transX = data[ 1 ];\r
219                                                         break;\r
220                                                 case 'translateY' :\r
221                                                         transY  = data[ 2 ];\r
222                                                         _transY = data[ 1 ];\r
223                                                         break;\r
224                                                 default :\r
225                                                         css[ type ] = data[ 1 ];\r
226                                                         prop[ prop.length ] = type;\r
227                                         };\r
228                                 };\r
229                                 if( transX || transY ){\r
230                                         prop[ prop.length ] = cssVendor + 'transform';\r
231                                         if( transX && transY ){\r
232                                                 css[ transform ] = cssVendor + 'translate(' + transX  + ',' + transY  + ')' + translateZ;\r
233                                                 this._transform  = cssVendor + 'translate(' + _transX + ',' + _transY + ')';\r
234                                                 \r
235                                         } else\r
236                                         if( transX ){\r
237                                                 css[ transform ] = cssVendor + 'translateX(' + transX + ')' + translateZ;\r
238                                                 this._transform  = cssVendor + 'translateX(' + _transX + ')';\r
239                                         } else\r
240                                         if( transY ){\r
241                                                 css[ transform ] = cssVendor + 'translateY(' + transY + ')' + translateZ;\r
242                                                 this._transform  = cssVendor + 'translateY(' + _transY + ')';\r
243                                         };                                      \r
244                                 };\r
245                                 this._transProp = prop.join( ',' );\r
246                         }) :\r
247                 transform ?\r
248                         (function( css, list, ratio, time, duration ){\r
249                                 var i = 0, l = list.length,\r
250                                         easing = this.timming,\r
251                                         data, type, transX, transY;\r
252                                 for( ; i < l; ++i ){\r
253                                         data = list[ i ];\r
254                                         type = data[ 0 ];\r
255                                         switch( type ){\r
256                                                 case 'translateX' :\r
257                                                         transX  = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
258                                                         break;\r
259                                                 case 'translateY' :\r
260                                                         transY  = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
261                                                         break;\r
262                                                 default :\r
263                                                         css[ type ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
264                                         };\r
265                                 };\r
266                                 if( transX && transY ){\r
267                                         this._transform  = cssVendor + 'translate(' + transX  + ',' + transY  + ')';\r
268                                         css[ transform ] = this._transform + translateZ;\r
269                                 } else\r
270                                 if( transX ){\r
271                                         this._transform  = cssVendor + 'translateX(' + transX + ')';\r
272                                         css[ transform ] = this._transform + translateZ;\r
273                                 } else\r
274                                 if( transY ){\r
275                                         this._transform  = cssVendor + 'translateY(' + transY + ')';\r
276                                         css[ transform ] = this._transform + translateZ;\r
277                                 };\r
278                         }) :\r
279                         (function( css, list, l, ratio, time, duration ){\r
280                                 var data,\r
281                                         easing = this.timming;\r
282                                 for( ; l; ){\r
283                                         data = list[ --l ];\r
284                                         css[ data[ 0 ] ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );\r
285                                 };\r
286                         })\r
287 });\r
288 \r
289 function Liner( t, b, c, d ){ \r
290   return c * t / d + b;\r
291 };\r
292 \r
293 // original :\r
294 // https://github.com/arian/cubic-bezier/blob/master/index.js, MIT License\r
295 function CubicBezier(x1, y1, x2, y2, t, epsilon){\r
296         var x = t, t0 = 0, t1 = 1, t2 = x, d2, i = 0;\r
297 \r
298         // First try a few iterations of Newton's method -- normally very fast.\r
299         for (; i < 8; i++){\r
300                 x2 = CubicBezier.curveX(x1,x2,t2) - x;\r
301                 if (ABS(x2) < epsilon) return CubicBezier.curveY(y1, y2, t2);\r
302                 d2 = CubicBezier.derivativeCurveX( x1, x2, t2);\r
303                 if (ABS(d2) < 1e-6) break;\r
304                 t2 = t2 - x2 / d2;\r
305         };\r
306         if (x < t0) return CubicBezier.curveY(y1, y2, t0);\r
307         if (x > t1) return CubicBezier.curveY(y1, y2, t1);\r
308         \r
309         t2 = x;\r
310         \r
311         // Fallback to the bisection method for reliability.\r
312         while (t0 < t1){\r
313                 x2 = CubicBezier.curveX(x1,x2,t2);\r
314                 if (ABS(x2 - x) < epsilon) return CubicBezier.curveY(y1, y2, t2);\r
315                 if (x > x2) t0 = t2;\r
316                 else t1 = t2;\r
317                 t2 = (t1 - t0) * .5 + t0;\r
318         };\r
319         // Failure\r
320         return CubicBezier.curveY(y1, y2, t2);\r
321 \r
322 };\r
323 CubicBezier.curveX = function( x1, x2, t ){\r
324         var v = 1 - t;\r
325         return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;\r
326 };\r
327 CubicBezier.curveY = function( y1, y2, t ){\r
328         var v = 1 - t;\r
329         return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;\r
330 };\r
331 CubicBezier.derivativeCurveX = function( x1, x2, t ){\r
332         var v = 1 - t;\r
333         return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2;\r
334 };\r
335 \r
336 \r
337 \r
338 // export\r
339 X.Dom.Node.prototype.anime = function(){\r
340         if( !this._anime ) this._anime = new AnimationQueue( this );\r
341         this._anime.init.apply( this._anime, arguments );\r
342         return this;\r
343 };\r
344 X.Dom.Node.prototype.stop = function(){\r
345         if( !this._anime ) return this;\r
346         this._anime.stop.apply( this._anime, arguments );\r
347         return this;\r
348 };\r
349 \r
350 return {\r
351         X       :  1,\r
352         Y       :  2,\r
353         MOVE    :  4,\r
354         WIDTH   :  8,\r
355         HEIGHT  : 16,\r
356         SIZE    : 32,\r
357         OPACITY : 64\r
358 };\r
359         \r
360 })( window, document, navigator, Math );\r
361 \r
362 \r