      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