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