1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @language=en
  9  * <iframe src='../../../examples/Tween.html?noHeader' width = '550' height = '130' scrolling='no'></iframe>
 10  * <br/>
 11  * Demo:
 12  * <pre>
 13  * ticker.addTick(Hilo.Tween);//Tween works after being added to ticker
 14  *
 15  * var view = new View({x:5, y:10});
 16  * Hilo.Tween.to(view, {
 17  *     x:100,
 18  *     y:20,
 19  *     alpha:0
 20  * }, {
 21  *     duration:1000,
 22  *     delay:500,
 23  *     ease:Hilo.Ease.Quad.EaseIn,
 24  *     onComplete:function(){
 25  *         console.log('complete');
 26  *     }
 27  * });
 28  * </pre>
 29  * @class Tween class makes tweening (easing, slow motion).
 30  * @param {Object} target Tween target object.
 31  * @param {Object} fromProps Beginning properties of target tweening object.
 32  * @param {Object} toProps Ending properties of target tweening object.
 33  * @param {Object} params Tweening parameters, include all writable Tween class properties.
 34  * @module hilo/tween/Tween
 35  * @requires hilo/core/Class
 36  * @property {Object} target Tween target object, readonly!
 37  * @property {Int} duration Tweening duration, measure in ms.
 38  * @property {Int} delay Tweenning delay time, measure in ms.
 39  * @property {Boolean} paused Is tweening paused, default value is false.
 40  * @property {Boolean} loop Does tweening loop, default value is false.
 41  * @property {Boolean} reverse Does tweening reverse, default value is false.
 42  * @property {Int} repeat Repeat times of tweening, default value is 0.
 43  * @property {Int} repeatDelay Delay time of repeating tweening, measure in ms.
 44  * @property {Function} ease Tweening transform function, default value is null.
 45  * @property {Int} time Time that tweening taken, measure in ms, readonly!
 46  * @property {Function} onStart Function invoked on the beginning of tweening. Require 1 parameter: tween. default value is null.
 47  * @property {Function} onUpdate Function invoked on tweening update. Require 2 parameters: ratio, tween.  default value is null.
 48  * @property {Function} onComplete Function invoked on the end of tweening. Require 1 parameter: tween.  default value is null.
 49  */
 50 var Tween = (function(){
 51 
 52 function now(){
 53     return +new Date();
 54 }
 55 
 56 return Class.create(/** @lends Tween.prototype */{
 57     constructor: function(target, fromProps, toProps, params){
 58         var me = this;
 59 
 60         me.target = target;
 61         me._startTime = 0;
 62         me._seekTime = 0;
 63         me._pausedTime = 0;
 64         me._pausedStartTime = 0;
 65         me._reverseFlag = 1;
 66         me._repeatCount = 0;
 67 
 68         //no fromProps if pass 3 arguments
 69         if(arguments.length == 3){
 70             params = toProps;
 71             toProps = fromProps;
 72             fromProps = null;
 73         }
 74 
 75         for(var p in params) me[p] = params[p];
 76         me.setProps(fromProps, 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: 0,
 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     onStart: null,
 97     onUpdate: null,
 98     onComplete: null,
 99 
100     /**
101      * @language=en
102      * Set beginning properties and ending properties of tweening object.
103      * @param {Object} fromProps Beginning properties of target tweening object.
104      * @param {Object} toProps Ending properties of target tweening object.
105      * @returns {Tween} Current Tween, for chain calls.
106      */
107     setProps: function(fromProps, toProps){
108         var me = this, target = me.target,
109             propNames = fromProps || toProps,
110             from = me._fromProps = {}, to = me._toProps = {};
111 
112         fromProps = fromProps || target;
113         toProps = toProps || target;
114 
115         for(var p in propNames){
116             to[p] = toProps[p] || 0;
117             target[p] = from[p] = fromProps[p] || 0;
118         }
119         return me;
120     },
121 
122     /**
123      * @language=en
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.paused = false;
133         Tween.add(me);
134         return me;
135     },
136 
137     /**
138      * @language=en
139      * Stop the tweening.
140      * @returns {Tween} Current Tween, for chain calls.
141      */
142     stop: function(){
143         Tween.remove(this);
144         return this;
145     },
146 
147     /**
148      * @language=en
149      * Pause the tweening.
150      * @returns {Tween} Current Tween, for chain calls.
151      */
152     pause: function(){
153         var me = this;
154         me.paused = true;
155         me._pausedStartTime = now();
156         return me;
157     },
158 
159     /**
160      * @language=en
161      * Continue to play the tweening.
162      * @returns {Tween} Current Tween, for chain calls.
163      */
164     resume: function(){
165         var me = this;
166         me.paused = false;
167         if(me._pausedStartTime) me._pausedTime += now() - me._pausedStartTime;
168         me._pausedStartTime = 0;
169         return me;
170     },
171 
172     /**
173      * @language=en
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      * @language=en
192      * 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.
193      * @param {Tween} tween Tween to link.
194      * @returns {Tween} Current Tween, for chain calls.
195      */
196     link: function(tween){
197         var me = this, delay = tween.delay, startTime = me._startTime;
198 
199         if(typeof delay === 'string'){
200             var plus = delay.indexOf('+') == 0, minus = delay.indexOf('-') == 0;
201             delay = plus || minus ? Number(delay.substr(1)) * (plus ? 1 : -1) : Number(delay);
202         }
203         tween.delay = delay;
204         tween._startTime = plus || minus ? startTime + me.duration + delay : startTime + delay;
205 
206         me._next = tween;
207         Tween.remove(tween);
208         return me;
209     },
210 
211     /**
212      * @language=en
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      * @language=en
223      * Private update method inside Tween class.
224      * @private
225      */
226     _update: function(time, forceUpdate){
227         var me = this;
228         if(me.paused && !forceUpdate) return;
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 : me.ease ? me.ease(ratio) : ratio;
237 
238         if(me.reverse){
239             //backward
240             if(me._reverseFlag < 0) ratio = 1 - ratio;
241             //forward
242             if(ratio < 1e-7){
243                 //repeat complete or not loop
244                 if((me.repeat > 0 && me._repeatCount++ >= me.repeat) || (me.repeat == 0 && !me.loop)){
245                     complete = true;
246                 }else{
247                     me._startTime = now();
248                     me._pausedTime = 0;
249                     me._reverseFlag *= -1;
250                 }
251             }
252         }
253 
254         //start callback
255         if(me.time == 0 && (callback = me.onStart)) callback.call(me, me);
256         me.time = elapsed;
257 
258         //render & update callback
259         me._render(ratio);
260         (callback = me.onUpdate) && callback.call(me, ratio, me);
261 
262         //check if complete
263         if(ratio >= 1){
264             if(me.reverse){
265                 me._startTime = now();
266                 me._pausedTime = 0;
267                 me._reverseFlag *= -1;
268             }else if(me.loop || me.repeat > 0 && me._repeatCount++ < me.repeat){
269                 me._startTime = now() + me.repeatDelay;
270                 me._pausedTime = 0;
271             }else{
272                 complete = true;
273             }
274         }
275 
276         //next tween
277         var next = me._next;
278         if(next && next.time <= 0){
279             var nextStartTime = next._startTime;
280             if(nextStartTime > 0 && nextStartTime <= time){
281                 //parallel tween
282                 next._render(ratio);
283                 next.time = elapsed;
284                 Tween.add(next);
285             }else if(complete && (nextStartTime < 0 || nextStartTime > time)){
286                 //next tween
287                 next.start();
288             }
289         }
290 
291         //complete
292         if(complete){
293             (callback = me.onComplete) && callback.call(me, me);
294             return true;
295         }
296     },
297 
298     Statics: /** @lends Tween */ {
299         /**
300          * @language=en
301          * @private
302          */
303         _tweens: [],
304 
305         /**
306          * @language=en
307          * Update all Tween instances.
308          * @returns {Object} Tween。
309          */
310         tick: function(){
311             var tweens = Tween._tweens, tween, i, len = tweens.length;
312 
313             for(i = 0; i < len; i++){
314                 tween = tweens[i];
315                 if(tween && tween._update(now())){
316                     tweens.splice(i, 1);
317                     i--;
318                 }
319             }
320             return Tween;
321         },
322 
323         /**
324          * @language=en
325          * Add a Tween instance.
326          * @param {Tween} tween Tween object to add.
327          * @returns {Object} Tween。
328          */
329         add: function(tween){
330             var tweens = Tween._tweens;
331             if(tweens.indexOf(tween) == -1) tweens.push(tween);
332             return Tween;
333         },
334 
335         /**
336          * @language=en
337          * Remove one Tween target.
338          * @param {Tween|Object|Array} tweenOrTarget Tween object, target object or an array of object to remove
339          * @returns {Object} Tween。
340          */
341         remove: function(tweenOrTarget){
342             if(tweenOrTarget instanceof Array){
343                 for(var i = 0, l = tweenOrTarget.length;i < l;i ++){
344                     Tween.remove(tweenOrTarget[i]);
345                 }
346                 return Tween;
347             }
348 
349             var tweens = Tween._tweens, i;
350             if(tweenOrTarget instanceof Tween){
351                 i = tweens.indexOf(tweenOrTarget);
352                 if(i > -1) tweens.splice(i, 1);
353             }else{
354                 for(i = 0; i < tweens.length; i++){
355                     if(tweens[i].target === tweenOrTarget){
356                         tweens.splice(i, 1);
357                         i--;
358                     }
359                 }
360             }
361 
362             return Tween;
363         },
364 
365         /**
366          * @language=en
367          * Remove all Tween instances.
368          * @returns {Object} Tween。
369          */
370         removeAll: function(){
371             Tween._tweens.length = 0;
372             return Tween;
373         },
374 
375         /**
376          * @language=en
377          * Create a tween, make target object easing from beginning properties to ending properties.
378          * @param {Object|Array} target Tweening target or tweening target array.
379          * @param fromProps Beginning properties of target tweening object.
380          * @param toProps Ending properties of target tweening object.
381          * @param params Tweening parameters.
382          * @returns {Tween|Array} An tween instance or an array of tween instance.
383          */
384         fromTo: function(target, fromProps, toProps, params){
385             var isArray = target instanceof Array;
386             target = isArray ? target : [target];
387 
388             var tween, i, stagger = params.stagger, tweens = [];
389             for(i = 0; i < target.length; i++){
390                 tween = new Tween(target[i], fromProps, toProps, params);
391                 if(stagger) tween.delay = (params.delay || 0) + (i * stagger || 0);
392                 tween.start();
393                 tweens.push(tween);
394             }
395 
396             return isArray?tweens:tween;
397         },
398 
399         /**
400          * @language=en
401          * Create a tween, make target object easing from current properties to ending properties.
402          * @param {Object|Array} target Tweening target or tweening target array.
403          * @param toProps Ending properties of target tweening object.
404          * @param params Tweening parameters.
405          * @returns {Tween|Array} An tween instance or an array of tween instance.
406          */
407         to: function(target, toProps, params){
408             return Tween.fromTo(target, null, toProps, params);
409         },
410 
411         /**
412          * @language=en
413          * Create a tween, make target object easing from beginning properties to current properties.
414          * @param {Object|Array} target Tweening target or tweening target array.
415          * @param fromProps Beginning properties of target tweening object.
416          * @param params Tweening parameters.
417          * @returns {Tween|Array} An tween instance or an array of tween instance.
418          */
419         from: function(target, fromProps, params){
420             return Tween.fromTo(target, fromProps, null, params);
421         }
422     }
423 
424 });
425 
426 })();
427