1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * <iframe src='../../../examples/Tween.html?noHeader' width = '550' height = '130' scrolling='no'></iframe>
  9  * <br/>
 10  * 使用示例:
 11  * <pre>
 12  * ticker.addTick(Hilo.Tween);//需要把Tween加到ticker里才能使用
 13  *
 14  * var view = new View({x:5, y:10});
 15  * Hilo.Tween.to(view, {
 16  *     x:100,
 17  *     y:20,
 18  *     alpha:0
 19  * }, {
 20  *     duration:1000,
 21  *     delay:500,
 22  *     ease:Hilo.Ease.Quad.EaseIn,
 23  *     onComplete:function(){
 24  *         console.log('complete');
 25  *     }
 26  * });
 27  * </pre>
 28  * @class Tween类提供缓动功能。
 29  * @param {Object} target 缓动对象。
 30  * @param {Object} fromProps 对象缓动的起始属性集合。
 31  * @param {Object} toProps 对象缓动的目标属性集合。
 32  * @param {Object} params 缓动参数。可包含Tween类所有可写属性。
 33  * @module hilo/tween/Tween
 34  * @requires hilo/core/Class
 35  * @property {Object} target 缓动目标。只读属性。
 36  * @property {Int} duration 缓动总时长。单位毫秒。
 37  * @property {Int} delay 缓动延迟时间。单位毫秒。
 38  * @property {Boolean} paused 缓动是否暂停。默认为false。
 39  * @property {Boolean} loop 缓动是否循环。默认为false。
 40  * @property {Boolean} reverse 缓动是否反转播放。默认为false。
 41  * @property {Int} repeat 缓动重复的次数。默认为0。
 42  * @property {Int} repeatDelay 缓动重复的延迟时长。单位为毫秒。
 43  * @property {Function} ease 缓动变化函数。默认为null。
 44  * @property {Int} time 缓动已进行的时长。单位毫秒。只读属性。
 45  * @property {Function} onStart 缓动开始回调函数。它接受1个参数:tween。默认值为null。
 46  * @property {Function} onUpdate 缓动更新回调函数。它接受2个参数:ratio和tween。默认值为null。
 47  * @property {Function} onComplete 缓动结束回调函数。它接受1个参数:tween。默认值为null。
 48  */
 49 var Tween = (function(){
 50 
 51 function now(){
 52     return +new Date();
 53 }
 54 
 55 return Class.create(/** @lends Tween.prototype */{
 56     constructor: function(target, fromProps, toProps, params){
 57         var me = this;
 58 
 59         me.target = target;
 60         me._startTime = 0;
 61         me._seekTime = 0;
 62         me._pausedTime = 0;
 63         me._pausedStartTime = 0;
 64         me._reverseFlag = 1;
 65         me._repeatCount = 0;
 66 
 67         //no fromProps if pass 3 arguments
 68         if(arguments.length == 3){
 69             params = toProps;
 70             toProps = fromProps;
 71             fromProps = null;
 72         }
 73 
 74         for(var p in params) me[p] = params[p];
 75         me._fromProps = fromProps;
 76         me._toProps = toProps;
 77 
 78         //for old version compatiblity
 79         if(!params.duration && params.time){
 80             me.duration = params.time || 0;
 81             me.time = 0;
 82         }
 83     },
 84 
 85     target: null,
 86     duration: 1000,
 87     delay: 0,
 88     paused: false,
 89     loop: false,
 90     reverse: false,
 91     repeat: 0,
 92     repeatDelay: 0,
 93     ease: null,
 94     time: 0, //ready only
 95 
 96     isStart:false,
 97     isComplete:false,
 98     onStart: null,
 99     onUpdate: null,
100     onComplete: null,
101 
102     /**
103      * 设置缓动对象的初始和目标属性。
104      * @param {Object} fromProps 缓动对象的初始属性。
105      * @param {Object} toProps 缓动对象的目标属性。
106      * @returns {Tween} Tween变换本身。可用于链式调用。
107      */
108     setProps: function(fromProps, toProps){
109         var me = this, target = me.target,
110             propNames = fromProps || toProps,
111             from = me._fromProps = {}, to = me._toProps = {};
112 
113         fromProps = fromProps || target;
114         toProps = toProps || target;
115 
116         for(var p in propNames){
117             to[p] = toProps[p] || 0;
118             target[p] = from[p] = fromProps[p] || 0;
119         }
120         return me;
121     },
122 
123     /**
124      * 启动缓动动画的播放。
125      * @returns {Tween} Tween变换本身。可用于链式调用。
126      */
127     start: function(){
128         var me = this;
129         me._startTime = now() + me.delay;
130         me._seekTime = 0;
131         me._pausedTime = 0;
132         me._reverseFlag = 1;
133         me._repeatCount = 0;
134         me.paused = false;
135         me.isStart = false;
136         me.isComplete = false;
137         Tween.add(me);
138         return me;
139     },
140 
141     /**
142      * 停止缓动动画的播放。
143      * @returns {Tween} Tween变换本身。可用于链式调用。
144      */
145     stop: function(){
146         Tween.remove(this);
147         return this;
148     },
149 
150     /**
151      * 暂停缓动动画的播放。
152      * @returns {Tween} Tween变换本身。可用于链式调用。
153      */
154     pause: function(){
155         var me = this;
156         me.paused = true;
157         me._pausedStartTime = now();
158         return me;
159     },
160 
161     /**
162      * 恢复缓动动画的播放。
163      * @returns {Tween} Tween变换本身。可用于链式调用。
164      */
165     resume: function(){
166         var me = this;
167         me.paused = false;
168         if(me._pausedStartTime) me._pausedTime += now() - me._pausedStartTime;
169         me._pausedStartTime = 0;
170         return me;
171     },
172 
173     /**
174      * 跳转Tween到指定的时间。
175      * @param {Number} time 指定要跳转的时间。取值范围为:0 - duraion。
176      * @param {Boolean} pause 是否暂停。
177      * @returns {Tween} Tween变换本身。可用于链式调用。
178      */
179     seek: function(time, pause){
180         var me = this, current = now();
181         me._startTime = current;
182         me._seekTime = time;
183         me._pausedTime = 0;
184         if(pause !== undefined) me.paused = pause;
185         me._update(current, true);
186         Tween.add(me);
187         return me;
188     },
189 
190     /**
191      * 连接下一个Tween变换。其开始时间根据delay值不同而不同。当delay值为字符串且以'+'或'-'开始时,Tween的开始时间从当前变换结束点计算,否则以当前变换起始点计算。
192      * @param {Tween} tween 要连接的Tween变换。
193      * @returns {Tween} 下一个Tween。可用于链式调用。
194      */
195     link: function(tween){
196         var me = this, delay = tween.delay, startTime = me._startTime;
197 
198         var plus, minus;
199         if(typeof delay === 'string'){
200             plus = delay.indexOf('+') == 0;
201             minus = delay.indexOf('-') == 0;
202             delay = plus || minus ? Number(delay.substr(1)) * (plus ? 1 : -1) : Number(delay);
203         }
204         tween.delay = delay;
205         tween._startTime = plus || minus ? startTime + me.duration + delay : startTime + delay;
206 
207         me._next = tween;
208         Tween.remove(tween);
209         return tween;
210     },
211 
212     /**
213      * Tween类的内部渲染方法。
214      * @private
215      */
216     _render: function(ratio){
217         var me = this, target = me.target, fromProps = me._fromProps, p;
218         for(p in fromProps) target[p] = fromProps[p] + (me._toProps[p] - fromProps[p]) * ratio;
219     },
220 
221     /**
222      * Tween类的内部更新方法。
223      * @private
224      */
225     _update: function(time, forceUpdate){
226         var me = this;
227         if(me.paused && !forceUpdate) return;
228         if(me.isComplete) return true;
229 
230         //elapsed time
231         var elapsed = time - me._startTime - me._pausedTime + me._seekTime;
232         if(elapsed < 0) return;
233 
234         //elapsed ratio
235         var ratio = elapsed / me.duration, complete = false, callback;
236         ratio = ratio <= 0 ? 0 : ratio >= 1 ? 1 : ratio;
237         var easeRatio = me.ease ? me.ease(ratio) : ratio;
238 
239         if(me.reverse && me.isStart){
240             //backward
241             if(me._reverseFlag < 0) {
242                 ratio = 1 - ratio;
243                 easeRatio = 1 - easeRatio;
244             }
245             //forward
246             if(ratio < 1e-7){
247                 //repeat complete or not loop
248                 if((me.repeat > 0 && me._repeatCount++ >= me.repeat) || (me.repeat == 0 && !me.loop)){
249                     complete = true;
250                 }else{
251                     me._startTime = now();
252                     me._pausedTime = 0;
253                     me._reverseFlag *= -1;
254                 }
255             }
256         }
257 
258         //start callback
259         if(!me.isStart) {
260             me.setProps(me._fromProps, me._toProps);
261             me.isStart = true;
262             if(me.onStart){
263                 me.onStart.call(me, me);
264             }
265         }
266         me.time = elapsed;
267 
268         //render & update callback
269         me._render(easeRatio);
270         (callback = me.onUpdate) && callback.call(me, ratio, me);
271 
272         //check if complete
273         if(ratio >= 1){
274             if(me.reverse){
275                 me._startTime = now();
276                 me._pausedTime = 0;
277                 me._reverseFlag *= -1;
278             }else if(me.loop || me.repeat > 0 && me._repeatCount++ < me.repeat){
279                 me._startTime = now() + me.repeatDelay;
280                 me._pausedTime = 0;
281             }else{
282                 me.isComplete = true;
283             }
284         }
285 
286         //next tween
287         var next = me._next;
288         if(next && next.time <= 0){
289             var nextStartTime = next._startTime;
290             if(nextStartTime > 0 && nextStartTime <= time){
291                 //parallel tween
292                 next._render(ratio);
293                 next.time = elapsed;
294                 Tween.add(next);
295             }else if(me.isComplete && (nextStartTime < 0 || nextStartTime > time)){
296                 //next tween
297                 next.start();
298             }
299         }
300 
301         //complete
302         if(me.isComplete){
303             (callback = me.onComplete) && callback.call(me, me);
304             return true;
305         }
306     },
307 
308     Statics: /** @lends Tween */ {
309         /**
310          * @private
311          */
312         _tweens: [],
313 
314         /**
315          * 更新所有Tween实例。
316          * @returns {Object} Tween。
317          */
318         tick: function(){
319             var tweens = Tween._tweens, tween, i, len = tweens.length;
320 
321             for(i = 0; i < len; i++){
322                 tween = tweens[i];
323                 if(tween && tween._update(now())){
324                     tweens.splice(i, 1);
325                     i--;
326                 }
327             }
328             return Tween;
329         },
330 
331         /**
332          * 添加Tween实例。
333          * @param {Tween} tween 要添加的Tween对象。
334          * @returns {Object} Tween。
335          */
336         add: function(tween){
337             var tweens = Tween._tweens;
338             if(tweens.indexOf(tween) == -1) tweens.push(tween);
339             return Tween;
340         },
341 
342         /**
343          * 删除Tween实例。
344          * @param {Tween|Object|Array} tweenOrTarget 要删除的Tween对象或target对象或要删除的一组对象。
345          * @returns {Object} Tween。
346          */
347         remove: function(tweenOrTarget){
348             var i, l;
349             if(tweenOrTarget instanceof Array){
350                 for(i = 0, l = tweenOrTarget.length;i < l;i ++){
351                     Tween.remove(tweenOrTarget[i]);
352                 }
353                 return Tween;
354             }
355 
356             var tweens = Tween._tweens;
357             if(tweenOrTarget instanceof Tween){
358                 i = tweens.indexOf(tweenOrTarget);
359                 if(i > -1) tweens.splice(i, 1);
360             }else{
361                 for(i = 0; i < tweens.length; i++){
362                     if(tweens[i].target === tweenOrTarget){
363                         tweens.splice(i, 1);
364                         i--;
365                     }
366                 }
367             }
368 
369             return Tween;
370         },
371 
372         /**
373          * 删除所有Tween实例。
374          * @returns {Object} Tween。
375          */
376         removeAll: function(){
377             Tween._tweens.length = 0;
378             return Tween;
379         },
380 
381         /**
382          * 创建一个缓动动画,让目标对象从开始属性变换到目标属性。
383          * @param {Object|Array} target 缓动目标对象或缓动目标数组。
384          * @param fromProps 缓动目标对象的开始属性。
385          * @param toProps 缓动目标对象的目标属性。
386          * @param params 缓动动画的参数。
387          * @returns {Tween|Array} 一个Tween实例对象或Tween实例数组。
388          */
389         fromTo: function(target, fromProps, toProps, params){
390             params = params || {};
391             var isArray = target instanceof Array;
392             target = isArray ? target : [target];
393 
394             var tween, i, stagger = params.stagger, tweens = [];
395             for(i = 0; i < target.length; i++){
396                 tween = new Tween(target[i], fromProps, toProps, params);
397                 if(stagger) tween.delay = (params.delay || 0) + (i * stagger || 0);
398                 tween.start();
399                 tweens.push(tween);
400             }
401 
402             return isArray?tweens:tween;
403         },
404 
405         /**
406          * 创建一个缓动动画,让目标对象从当前属性变换到目标属性。
407          * @param {Object|Array} target 缓动目标对象或缓动目标数组。
408          * @param toProps 缓动目标对象的目标属性。
409          * @param params 缓动动画的参数。
410          * @returns {Tween|Array} 一个Tween实例对象或Tween实例数组。
411          */
412         to: function(target, toProps, params){
413             return Tween.fromTo(target, null, toProps, params);
414         },
415 
416         /**
417          * 创建一个缓动动画,让目标对象从指定的起始属性变换到当前属性。
418          * @param {Object|Array} target 缓动目标对象或缓动目标数组。
419          * @param fromProps 缓动目标对象的初始属性。
420          * @param params 缓动动画的参数。
421          * @returns {Tween|Array} 一个Tween实例对象或Tween实例数组。
422          */
423         from: function(target, fromProps, params){
424             return Tween.fromTo(target, fromProps, null, params);
425         }
426     }
427 
428 });
429 
430 })();
431