      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  * Demo:
     11  * <pre>
     12  * ticker.addTick(Hilo.Tween);//Tween works after being added to 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 class makes tweening (easing, slow motion).
     29  * @param {Object} target Tween target object.
     30  * @param {Object} fromProps Beginning properties of target tweening object.
     31  * @param {Object} toProps Ending properties of target tweening object.
     32  * @param {Object} params Tweening parameters, include all writable Tween class properties.
     33  * @module hilo/tween/Tween
     34  * @requires hilo/core/Class
     35  * @property {Object} target Tween target object, readonly!
     36  * @property {Int} duration Tweening duration, measure in ms.
     37  * @property {Int} delay Tweenning delay time, measure in ms.
     38  * @property {Boolean} paused Is tweening paused, default value is false.
     39  * @property {Boolean} loop Does tweening loop, default value is false.
     40  * @property {Boolean} reverse Does tweening reverse, default value is false.
     41  * @property {Int} repeat Repeat times of tweening, default value is 0.
     42  * @property {Int} repeatDelay Delay time of repeating tweening, measure in ms.
     43  * @property {Function} ease Tweening transform function, default value is null.
     44  * @property {Int} time Time that tweening taken, measure in ms, readonly!
     45  * @property {Function} onStart Function invoked on the beginning of tweening. Require 1 parameter: tween. default value is null.
     46  * @property {Function} onUpdate Function invoked on tweening update. Require 2 parameters: ratio, tween.  default value is null.
     47  * @property {Function} onComplete Function invoked on the end of tweening. Require 1 parameter: tween.  default value is 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      * Set beginning properties and ending properties of tweening object.
    104      * @param {Object} fromProps Beginning properties of target tweening object.
    105      * @param {Object} toProps Ending properties of target tweening object.
    106      * @returns {Tween} Current Tween, for chain calls.
    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      * Starting the tweening.
    125      * @returns {Tween} Current Tween, for chain calls.
    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      * Stop the tweening.
    143      * @returns {Tween} Current Tween, for chain calls.
    144      */
    145     stop: function(){
    146         Tween.remove(this);
    147         return this;
    148     },
    149
    150     /**
    151      * Pause the tweening.
    152      * @returns {Tween} Current Tween, for chain calls.
    153      */
    154     pause: function(){
    155         var me = this;
    156         me.paused = true;
    157         me._pausedStartTime = now();
    158         return me;
    159     },
    160
    161     /**
    162      * Continue to play the tweening.
    163      * @returns {Tween} Current Tween, for chain calls.
    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 jumps to some point.
    175      * @param {Number} time The time to jump to, range from 0 to duration.
    176      * @param {Boolean} pause Is paused.
    177      * @returns {Tween} Current Tween, for chain calls.
    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      * Link next Tween. The beginning time of next Tween depends on the delay value. If delay is a string that begins with '+' or '-', next Tween will begin at (delay) ms after or before the current tween is ended. If delay is out of previous situation, next Tween will begin at (delay) ms after the beginning point of current Tween.
    192      * @param {Tween} tween Tween to link.
    193      * @returns {Tween} next Tween, for chain calls.
    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      * Private render method inside Tween class.
    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      * Private update method inside Tween class.
    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          * Update all Tween instances.
    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          * Add a Tween instance.
    333          * @param {Tween} tween Tween object to add.
    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          * Remove one Tween target.
    344          * @param {Tween|Object|Array} tweenOrTarget Tween object, target object or an array of object to remove
    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          * Remove all Tween instances.
    374          * @returns {Object} Tween。
    375          */
    376         removeAll: function(){
    377             Tween._tweens.length = 0;
    378             return Tween;
    379         },
    380
    381         /**
    382          * Create a tween, make target object easing from beginning properties to ending properties.
    383          * @param {Object|Array} target Tweening target or tweening target array.
    384          * @param fromProps Beginning properties of target tweening object.
    385          * @param toProps Ending properties of target tweening object.
    386          * @param params Tweening parameters.
    387          * @returns {Tween|Array} An tween instance or an array of tween instance.
    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          * Create a tween, make target object easing from current properties to ending properties.
    407          * @param {Object|Array} target Tweening target or tweening target array.
    408          * @param toProps Ending properties of target tweening object.
    409          * @param params Tweening parameters.
    410          * @returns {Tween|Array} An tween instance or an array of tween instance.
    411          */
    412         to: function(target, toProps, params){
    413             return Tween.fromTo(target, null, toProps, params);
    414         },
    415
    416         /**
    417          * Create a tween, make target object easing from beginning properties to current properties.
    418          * @param {Object|Array} target Tweening target or tweening target array.
    419          * @param fromProps Beginning properties of target tweening object.
    420          * @param params Tweening parameters.
    421          * @returns {Tween|Array} An tween instance or an array of tween instance.
    422          */
    423         from: function(target, fromProps, params){
    424             return Tween.fromTo(target, fromProps, null, params);
    425         }
    426     }
    427
    428 });
    429
    430 })();
    431