2 * GPU レイヤーを使うとスムースなアニメーションが得られる
\r
3 * 但し、古めのデバイスでは処理落ちが起こる
\r
4 * 処理落ちは、最も古い初代 iPod touch (iOS3) では特に顕著に発生するため、テスト機としてちょうどいい
\r
5 * css の書き方によっても、処理落ちやガタツキに影響するので、デザイナーが気軽に触れるものではない
\r
7 * transform の登場によって、要素の表示位置を変更する方法が、css-p との2択になった。
\r
8 * 初代 iPod touch では、簡単な css-p すら無視してくれるので、積極的に transform を使っていけ、ということだと思う。
\r
9 * 但し transform には様々な癖があり、一筋縄ではいかない。
\r
11 * DHTML を扱う専用モジュールを用意して、そこにコードとノウハウを集積する。
\r
14 * 新しいアニメーション・キューが追加されたときに、現在アニメーションしている要素と親子関係ならば、以前のアニメーションは直ちに停止し、GPUレイヤーを解除。
\r
17 X.Dom.Anime = (function( window, document, navigator, Math, undefined ){
\r
19 var QUEUE_LIST = [],
\r
21 ABS = new Function( 'v', 'return v<0?-v:v' ),
\r
22 animeEnabled = !X.UA.EInk,
\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
33 * 新規アニメーション要素が現在アニメーションしている要素と親子関係の場合、
\r
34 * 親子関係のアニメーションを停止して一挙に最後の状態へ
\r
36 function registerQueue( queue ){
\r
37 var list = QUEUE_LIST,
\r
39 xnode = queue.xnode,
\r
44 _xnode !== xnode && ( _xnode.contains( xnode ) || xnode.contains( _xnode ) ) && q.stop( true );
\r
46 list[ list.length ] = queue;
\r
47 !timerID && ( timerID = X.Timer.nextFrame( onEnterFrame ) );
\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
57 QUEUE_LIST.splice( i, 1 );
\r
61 function onEnterFrame(){
\r
62 var list = QUEUE_LIST,
\r
67 list[ --l ].tick( t );
\r
69 list.length && ( timerID = X.Timer.nextFrame( onEnterFrame ) );
\r
72 var AnimationQueue = function( xnode ){
\r
76 X.Class._override( AnimationQueue.prototype,
\r
78 state : 0, // 0:停止, 1:登録済, 2:アニメ中の親子要素のGPUレイヤー解除待ち, 3:アニメ中;GPUレイヤーにセット、目標値のセット(transisiton有効), 4:
\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
97 prop[ 2 ] = this.xnode.width(); // currentValue
\r
100 prop[ 2 ] = this.xnode.height(); // currentValue
\r
104 type = transform ? 'translateX' : 'left';
\r
105 prop[ 2 ] = this.xnode.x(); // currentValue
\r
109 type = transform ? 'translateY' : 'top';
\r
110 prop[ 2 ] = this.xnode.y(); // currentValue
\r
115 prop[ 1 ] = data[ type ]; // target value;
\r
116 prop[ 3 ] = prop[ 1 ] - prop[ 2 ];
\r
119 this.state === 0 && registerQueue( this );
\r
122 this.startTime = X.getTime();
\r
123 this.duration = duration;
\r
125 this.timming = 'liner';//timmingFunction;
\r
127 this.timming = Liner;
\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
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
142 var ratio = ( time - this.startTime ) / this.duration,
\r
144 xnode = this.xnode,
\r
146 if( this.state === 1 ){
\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
155 delete this._transProp;
\r
157 if( this.state === 2 ){
\r
160 css[ transform ] = this._transform + translateZ;
\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
168 css[ transform ] = this._transform; // GPU support off
\r
169 delete this._transform;
\r
172 .unlisten( transition.End, xnode, this.onTransitionEnd );
\r
174 xnode.dispatch( { type : X.Dom.Event.ANIME, ratio : rario, target : xnode } );
\r
178 var offset = time - this.startTime,
\r
179 ratio = offset / this.duration,
\r
181 xnode = this.xnode,
\r
185 calculateCSS( css = {}, list, 1, offset, this.duration );
\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
192 calculateCSS( css = {}, list, ratio, offset, this.duration );
\r
194 .dispatch( { type : X.Dom.Event.ANIME, ratio : rario, target : xnode } );
\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
202 * transisitonProperty を集める
\r
207 (function( css, list ){
\r
211 data, type, transX, transY, _transX, _transY;
\r
212 for( ; i < l; ++i ){
\r
216 case 'translateX' :
\r
217 transX = data[ 2 ];
\r
218 _transX = data[ 1 ];
\r
220 case 'translateY' :
\r
221 transY = data[ 2 ];
\r
222 _transY = data[ 1 ];
\r
225 css[ type ] = data[ 1 ];
\r
226 prop[ prop.length ] = type;
\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
237 css[ transform ] = cssVendor + 'translateX(' + transX + ')' + translateZ;
\r
238 this._transform = cssVendor + 'translateX(' + _transX + ')';
\r
241 css[ transform ] = cssVendor + 'translateY(' + transY + ')' + translateZ;
\r
242 this._transform = cssVendor + 'translateY(' + _transY + ')';
\r
245 this._transProp = prop.join( ',' );
\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
256 case 'translateX' :
\r
257 transX = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
259 case 'translateY' :
\r
260 transY = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
263 css[ type ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
266 if( transX && transY ){
\r
267 this._transform = cssVendor + 'translate(' + transX + ',' + transY + ')';
\r
268 css[ transform ] = this._transform + translateZ;
\r
271 this._transform = cssVendor + 'translateX(' + transX + ')';
\r
272 css[ transform ] = this._transform + translateZ;
\r
275 this._transform = cssVendor + 'translateY(' + transY + ')';
\r
276 css[ transform ] = this._transform + translateZ;
\r
279 (function( css, list, l, ratio, time, duration ){
\r
281 easing = this.timming;
\r
283 data = list[ --l ];
\r
284 css[ data[ 0 ] ] = ratio === 1 ? data[ 1 ] : easing( time, data[ 2 ], data[ 3 ], duration );
\r
289 function Liner( t, b, c, d ){
\r
290 return c * t / d + b;
\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
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
306 if (x < t0) return CubicBezier.curveY(y1, y2, t0);
\r
307 if (x > t1) return CubicBezier.curveY(y1, y2, t1);
\r
311 // Fallback to the bisection method for reliability.
\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
317 t2 = (t1 - t0) * .5 + t0;
\r
320 return CubicBezier.curveY(y1, y2, t2);
\r
323 CubicBezier.curveX = function( x1, x2, t ){
\r
325 return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
\r
327 CubicBezier.curveY = function( y1, y2, t ){
\r
329 return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
\r
331 CubicBezier.derivativeCurveX = function( x1, x2, t ){
\r
333 return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2;
\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
344 X.Dom.Node.prototype.stop = function(){
\r
345 if( !this._anime ) return this;
\r
346 this._anime.stop.apply( this._anime, arguments );
\r
360 })( window, document, navigator, Math );
\r