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.setProps(fromProps, toProps);
 76 
 77         //for old version compatiblity
 78         if(!params.duration && params.time){
 79             me.duration = params.time || 0;
 80             me.time = 0;
 81         }
 82     },
 83 
 84     target: null,
 85     duration: 0,
 86     delay: 0,
 87     paused: false,
 88     loop: false,
 89     reverse: false,
 90     repeat: 0,
 91     repeatDelay: 0,
 92     ease: null,
 93     time: 0, //ready only
 94 
 95     onStart: null,
 96     onUpdate: null,
 97     onComplete: null,
 98 
 99     /**
100      * 设置缓动对象的初始和目标属性。
101      * @param {Object} fromProps 缓动对象的初始属性。
102      * @param {Object} toProps 缓动对象的目标属性。
103      * @returns {Tween} Tween变换本身。可用于链式调用。
104      */
105     setProps: function(fromProps, toProps){
106         var me = this, target = me.target,
107             propNames = fromProps || toProps,
108             from = me._fromProps = {}, to = me._toProps = {};
109 
110         fromProps = fromProps || target;
111         toProps = toProps || target;
112 
113         for(var p in propNames){
114             to[p] = toProps[p] || 0;
115             target[p] = from[p] = fromProps[p] || 0;
116         }
117         return me;
118     },
119 
120     /**
121      * 启动缓动动画的播放。
122      * @returns {Tween} Tween变换本身。可用于链式调用。
123      */
124     start: function(){
125         var me = this;
126         me._startTime = now() + me.delay;
127         me._seekTime = 0;
128         me._pausedTime = 0;
129         me.paused = false;
130         Tween.add(me);
131         return me;
132     },
133 
134     /**
135      * 停止缓动动画的播放。
136      * @returns {Tween} Tween变换本身。可用于链式调用。
137      */
138     stop: function(){
139         Tween.remove(this);
140         return this;
141     },
142 
143     /**
144      * 暂停缓动动画的播放。
145      * @returns {Tween} Tween变换本身。可用于链式调用。
146      */
147     pause: function(){
148         var me = this;
149         me.paused = true;
150         me._pausedStartTime = now();
151         return me;
152     },
153 
154     /**
155      * 恢复缓动动画的播放。
156      * @returns {Tween} Tween变换本身。可用于链式调用。
157      */
158     resume: function(){
159         var me = this;
160         me.paused = false;
161         if(me._pausedStartTime) me._pausedTime += now() - me._pausedStartTime;
162         me._pausedStartTime = 0;
163         return me;
164     },
165 
166     /**
167      * 跳转Tween到指定的时间。
168      * @param {Number} time 指定要跳转的时间。取值范围为:0 - duraion。
169      * @param {Boolean} pause 是否暂停。
170      * @returns {Tween} Tween变换本身。可用于链式调用。
171      */
172     seek: function(time, pause){
173         var me = this, current = now();
174         me._startTime = current;
175         me._seekTime = time;
176         me._pausedTime = 0;
177         if(pause !== undefined) me.paused = pause;
178         me._update(current, true);
179         Tween.add(me);
180         return me;
181     },
182 
183     /**
184      * 连接下一个Tween变换。其开始时间根据delay值不同而不同。当delay值为字符串且以'+'或'-'开始时,Tween的开始时间从当前变换结束点计算,否则以当前变换起始点计算。
185      * @param {Tween} tween 要连接的Tween变换。
186      * @returns {Tween} Tween变换本身。可用于链式调用。
187      */
188     link: function(tween){
189         var me = this, delay = tween.delay, startTime = me._startTime;
190 
191         if(typeof delay === 'string'){
192             var plus = delay.indexOf('+') == 0, minus = delay.indexOf('-') == 0;
193             delay = plus || minus ? Number(delay.substr(1)) * (plus ? 1 : -1) : Number(delay);
194         }
195         tween.delay = delay;
196         tween._startTime = plus || minus ? startTime + me.duration + delay : startTime + delay;
197 
198         me._next = tween;
199         Tween.remove(tween);
200         return me;
201     },
202 
203     /**
204      * Tween类的内部渲染方法。
205      * @private
206      */
207     _render: function(ratio){
208         var me = this, target = me.target, fromProps = me._fromProps, p;
209         for(p in fromProps) target[p] = fromProps[p] + (me._toProps[p] - fromProps[p]) * ratio;
210     },
211 
212     /**
213      * Tween类的内部更新方法。
214      * @private
215      */
216     _update: function(time, forceUpdate){
217         var me = this;
218         if(me.paused && !forceUpdate) return;
219 
220         //elapsed time
221         var elapsed = time - me._startTime - me._pausedTime + me._seekTime;
222         if(elapsed < 0) return;
223 
224         //elapsed ratio
225         var ratio = elapsed / me.duration, complete = false, callback;
226         ratio = ratio <= 0 ? 0 : ratio >= 1 ? 1 : me.ease ? me.ease(ratio) : ratio;
227 
228         if(me.reverse){
229             //backward
230             if(me._reverseFlag < 0) ratio = 1 - ratio;
231             //forward
232             if(ratio < 1e-7){
233                 //repeat complete or not loop
234                 if((me.repeat > 0 && me._repeatCount++ >= me.repeat) || (me.repeat == 0 && !me.loop)){
235                     complete = true;
236                 }else{
237                     me._startTime = now();
238                     me._pausedTime = 0;
239                     me._reverseFlag *= -1;
240                 }
241             }
242         }
243 
244         //start callback
245         if(me.time == 0 && (callback = me.onStart)) callback.call(me, me);
246         me.time = elapsed;
247 
248         //render & update callback
249         me._render(ratio);
250         (callback = me.onUpdate) && callback.call(me, ratio, me);
251 
252         //check if complete
253         if(ratio >= 1){
254             if(me.reverse){
255                 me._startTime = now();
256                 me._pausedTime = 0;
257                 me._reverseFlag *= -1;
258             }else if(me.loop || me.repeat > 0 && me._repeatCount++ < me.repeat){
259                 me._startTime = now() + me.repeatDelay;
260                 me._pausedTime = 0;
261             }else{
262                 complete = true;
263             }
264         }
265 
266         //next tween
267         var next = me._next;
268         if(next && next.time <= 0){
269             var nextStartTime = next._startTime;
270             if(nextStartTime > 0 && nextStartTime <= time){
271                 //parallel tween
272                 next._render(ratio);
273                 next.time = elapsed;
274                 Tween.add(next);
275             }else if(complete && (nextStartTime < 0 || nextStartTime > time)){
276                 //next tween
277                 next.start();
278             }
279         }
280 
281         //complete
282         if(complete){
283             (callback = me.onComplete) && callback.call(me, me);
284             return true;
285         }
286     },
287 
288     Statics: /** @lends Tween */ {
289         /**
290          * @private
291          */
292         _tweens: [],
293 
294         /**
295          * 更新所有Tween实例。
296          * @returns {Object} Tween。
297          */
298         tick: function(){
299             var tweens = Tween._tweens, tween, i, len = tweens.length;
300 
301             for(i = 0; i < len; i++){
302                 tween = tweens[i];
303                 if(tween && tween._update(now())){
304                     tweens.splice(i, 1);
305                     i--;
306                 }
307             }
308             return Tween;
309         },
310 
311         /**
312          * 添加Tween实例。
313          * @param {Tween} tween 要添加的Tween对象。
314          * @returns {Object} Tween。
315          */
316         add: function(tween){
317             var tweens = Tween._tweens;
318             if(tweens.indexOf(tween) == -1) tweens.push(tween);
319             return Tween;
320         },
321 
322         /**
323          * 删除Tween实例。
324          * @param {Tween|Object|Array} tweenOrTarget 要删除的Tween对象或target对象或要删除的一组对象。
325          * @returns {Object} Tween。
326          */
327         remove: function(tweenOrTarget){
328             if(tweenOrTarget instanceof Array){
329                 for(var i = 0, l = tweenOrTarget.length;i < l;i ++){
330                     Tween.remove(tweenOrTarget[i]);
331                 }
332                 return Tween;
333             }
334 
335             var tweens = Tween._tweens, i;
336             if(tweenOrTarget instanceof Tween){
337                 i = tweens.indexOf(tweenOrTarget);
338                 if(i > -1) tweens.splice(i, 1);
339             }else{
340                 for(i = 0; i < tweens.length; i++){
341                     if(tweens[i].target === tweenOrTarget){
342                         tweens.splice(i, 1);
343                         i--;
344                     }
345                 }
346             }
347 
348             return Tween;
349         },
350 
351         /**
352          * 删除所有Tween实例。
353          * @returns {Object} Tween。
354          */
355         removeAll: function(){
356             Tween._tweens.length = 0;
357             return Tween;
358         },
359 
360         /**
361          * 创建一个缓动动画,让目标对象从开始属性变换到目标属性。
362          * @param {Object|Array} target 缓动目标对象或缓动目标数组。
363          * @param fromProps 缓动目标对象的开始属性。
364          * @param toProps 缓动目标对象的目标属性。
365          * @param params 缓动动画的参数。
366          * @returns {Tween|Array} 一个Tween实例对象或Tween实例数组。
367          */
368         fromTo: function(target, fromProps, toProps, params){
369             var isArray = target instanceof Array;
370             target = isArray ? target : [target];
371 
372             var tween, i, stagger = params.stagger, tweens = [];
373             for(i = 0; i < target.length; i++){
374                 tween = new Tween(target[i], fromProps, toProps, params);
375                 if(stagger) tween.delay = (params.delay || 0) + (i * stagger || 0);
376                 tween.start();
377                 tweens.push(tween);
378             }
379 
380             return isArray?tweens:tween;
381         },
382 
383         /**
384          * 创建一个缓动动画,让目标对象从当前属性变换到目标属性。
385          * @param {Object|Array} target 缓动目标对象或缓动目标数组。
386          * @param toProps 缓动目标对象的目标属性。
387          * @param params 缓动动画的参数。
388          * @returns {Tween|Array} 一个Tween实例对象或Tween实例数组。
389          */
390         to: function(target, toProps, params){
391             return Tween.fromTo(target, null, toProps, params);
392         },
393 
394         /**
395          * 创建一个缓动动画,让目标对象从指定的起始属性变换到当前属性。
396          * @param {Object|Array} target 缓动目标对象或缓动目标数组。
397          * @param fromProps 缓动目标对象的目标属性。
398          * @param params 缓动动画的参数。
399          * @returns {Tween|Array} 一个Tween实例对象或Tween实例数组。
400          */
401         from: function(target, fromProps, params){
402             return Tween.fromTo(target, fromProps, null, params);
403         }
404     }
405 
406 });
407 
408 })();