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