OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 Google Inc. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 (function(shared, scope, testing) { |
| 16 scope.animationsWithPromises = []; |
| 17 |
| 18 scope.Animation = function(effect, timeline) { |
| 19 this.id = ''; |
| 20 if (effect && effect._id) { |
| 21 this.id = effect._id; |
| 22 } |
| 23 this.effect = effect; |
| 24 if (effect) { |
| 25 effect._animation = this; |
| 26 } |
| 27 if (!timeline) { |
| 28 throw new Error('Animation with null timeline is not supported'); |
| 29 } |
| 30 this._timeline = timeline; |
| 31 this._sequenceNumber = shared.sequenceNumber++; |
| 32 this._holdTime = 0; |
| 33 this._paused = false; |
| 34 this._isGroup = false; |
| 35 this._animation = null; |
| 36 this._childAnimations = []; |
| 37 this._callback = null; |
| 38 this._oldPlayState = 'idle'; |
| 39 this._rebuildUnderlyingAnimation(); |
| 40 // Animations are constructed in the idle state. |
| 41 this._animation.cancel(); |
| 42 this._updatePromises(); |
| 43 }; |
| 44 |
| 45 scope.Animation.prototype = { |
| 46 _updatePromises: function() { |
| 47 var oldPlayState = this._oldPlayState; |
| 48 var newPlayState = this.playState; |
| 49 if (this._readyPromise && newPlayState !== oldPlayState) { |
| 50 if (newPlayState == 'idle') { |
| 51 this._rejectReadyPromise(); |
| 52 this._readyPromise = undefined; |
| 53 } else if (oldPlayState == 'pending') { |
| 54 this._resolveReadyPromise(); |
| 55 } else if (newPlayState == 'pending') { |
| 56 this._readyPromise = undefined; |
| 57 } |
| 58 } |
| 59 if (this._finishedPromise && newPlayState !== oldPlayState) { |
| 60 if (newPlayState == 'idle') { |
| 61 this._rejectFinishedPromise(); |
| 62 this._finishedPromise = undefined; |
| 63 } else if (newPlayState == 'finished') { |
| 64 this._resolveFinishedPromise(); |
| 65 } else if (oldPlayState == 'finished') { |
| 66 this._finishedPromise = undefined; |
| 67 } |
| 68 } |
| 69 this._oldPlayState = this.playState; |
| 70 return (this._readyPromise || this._finishedPromise); |
| 71 }, |
| 72 _rebuildUnderlyingAnimation: function() { |
| 73 this._updatePromises(); |
| 74 var oldPlaybackRate; |
| 75 var oldPaused; |
| 76 var oldStartTime; |
| 77 var oldCurrentTime; |
| 78 var hadUnderlying = this._animation ? true : false; |
| 79 if (hadUnderlying) { |
| 80 oldPlaybackRate = this.playbackRate; |
| 81 oldPaused = this._paused; |
| 82 oldStartTime = this.startTime; |
| 83 oldCurrentTime = this.currentTime; |
| 84 this._animation.cancel(); |
| 85 this._animation._wrapper = null; |
| 86 this._animation = null; |
| 87 } |
| 88 |
| 89 if (!this.effect || this.effect instanceof window.KeyframeEffect) { |
| 90 this._animation = scope.newUnderlyingAnimationForKeyframeEffect(this.eff
ect); |
| 91 scope.bindAnimationForKeyframeEffect(this); |
| 92 } |
| 93 if (this.effect instanceof window.SequenceEffect || this.effect instanceof
window.GroupEffect) { |
| 94 this._animation = scope.newUnderlyingAnimationForGroup(this.effect); |
| 95 scope.bindAnimationForGroup(this); |
| 96 } |
| 97 if (this.effect && this.effect._onsample) { |
| 98 scope.bindAnimationForCustomEffect(this); |
| 99 } |
| 100 if (hadUnderlying) { |
| 101 if (oldPlaybackRate != 1) { |
| 102 this.playbackRate = oldPlaybackRate; |
| 103 } |
| 104 if (oldStartTime !== null) { |
| 105 this.startTime = oldStartTime; |
| 106 } else if (oldCurrentTime !== null) { |
| 107 this.currentTime = oldCurrentTime; |
| 108 } else if (this._holdTime !== null) { |
| 109 this.currentTime = this._holdTime; |
| 110 } |
| 111 if (oldPaused) { |
| 112 this.pause(); |
| 113 } |
| 114 } |
| 115 this._updatePromises(); |
| 116 }, |
| 117 _updateChildren: function() { |
| 118 if (!this.effect || this.playState == 'idle') |
| 119 return; |
| 120 |
| 121 var offset = this.effect._timing.delay; |
| 122 this._childAnimations.forEach(function(childAnimation) { |
| 123 this._arrangeChildren(childAnimation, offset); |
| 124 if (this.effect instanceof window.SequenceEffect) |
| 125 offset += scope.groupChildDuration(childAnimation.effect); |
| 126 }.bind(this)); |
| 127 }, |
| 128 _setExternalAnimation: function(animation) { |
| 129 if (!this.effect || !this._isGroup) |
| 130 return; |
| 131 for (var i = 0; i < this.effect.children.length; i++) { |
| 132 this.effect.children[i]._animation = animation; |
| 133 this._childAnimations[i]._setExternalAnimation(animation); |
| 134 } |
| 135 }, |
| 136 _constructChildAnimations: function() { |
| 137 if (!this.effect || !this._isGroup) |
| 138 return; |
| 139 var offset = this.effect._timing.delay; |
| 140 this._removeChildAnimations(); |
| 141 this.effect.children.forEach(function(child) { |
| 142 var childAnimation = scope.timeline._play(child); |
| 143 this._childAnimations.push(childAnimation); |
| 144 childAnimation.playbackRate = this.playbackRate; |
| 145 if (this._paused) |
| 146 childAnimation.pause(); |
| 147 child._animation = this.effect._animation; |
| 148 |
| 149 this._arrangeChildren(childAnimation, offset); |
| 150 |
| 151 if (this.effect instanceof window.SequenceEffect) |
| 152 offset += scope.groupChildDuration(child); |
| 153 }.bind(this)); |
| 154 }, |
| 155 _arrangeChildren: function(childAnimation, offset) { |
| 156 if (this.startTime === null) { |
| 157 childAnimation.currentTime = this.currentTime - offset / this.playbackRa
te; |
| 158 } else if (childAnimation.startTime !== this.startTime + offset / this.pla
ybackRate) { |
| 159 childAnimation.startTime = this.startTime + offset / this.playbackRate; |
| 160 } |
| 161 }, |
| 162 get timeline() { |
| 163 return this._timeline; |
| 164 }, |
| 165 get playState() { |
| 166 return this._animation ? this._animation.playState : 'idle'; |
| 167 }, |
| 168 get finished() { |
| 169 if (!window.Promise) { |
| 170 console.warn('Animation Promises require JavaScript Promise constructor'
); |
| 171 return null; |
| 172 } |
| 173 if (!this._finishedPromise) { |
| 174 if (scope.animationsWithPromises.indexOf(this) == -1) { |
| 175 scope.animationsWithPromises.push(this); |
| 176 } |
| 177 this._finishedPromise = new Promise( |
| 178 function(resolve, reject) { |
| 179 this._resolveFinishedPromise = function() { |
| 180 resolve(this); |
| 181 }; |
| 182 this._rejectFinishedPromise = function() { |
| 183 reject({type: DOMException.ABORT_ERR, name: 'AbortError'}); |
| 184 }; |
| 185 }.bind(this)); |
| 186 if (this.playState == 'finished') { |
| 187 this._resolveFinishedPromise(); |
| 188 } |
| 189 } |
| 190 return this._finishedPromise; |
| 191 }, |
| 192 get ready() { |
| 193 if (!window.Promise) { |
| 194 console.warn('Animation Promises require JavaScript Promise constructor'
); |
| 195 return null; |
| 196 } |
| 197 if (!this._readyPromise) { |
| 198 if (scope.animationsWithPromises.indexOf(this) == -1) { |
| 199 scope.animationsWithPromises.push(this); |
| 200 } |
| 201 this._readyPromise = new Promise( |
| 202 function(resolve, reject) { |
| 203 this._resolveReadyPromise = function() { |
| 204 resolve(this); |
| 205 }; |
| 206 this._rejectReadyPromise = function() { |
| 207 reject({type: DOMException.ABORT_ERR, name: 'AbortError'}); |
| 208 }; |
| 209 }.bind(this)); |
| 210 if (this.playState !== 'pending') { |
| 211 this._resolveReadyPromise(); |
| 212 } |
| 213 } |
| 214 return this._readyPromise; |
| 215 }, |
| 216 get onfinish() { |
| 217 return this._animation.onfinish; |
| 218 }, |
| 219 set onfinish(v) { |
| 220 if (typeof v == 'function') { |
| 221 this._animation.onfinish = (function(e) { |
| 222 e.target = this; |
| 223 v.call(this, e); |
| 224 }).bind(this); |
| 225 } else { |
| 226 this._animation.onfinish = v; |
| 227 } |
| 228 }, |
| 229 get oncancel() { |
| 230 return this._animation.oncancel; |
| 231 }, |
| 232 set oncancel(v) { |
| 233 if (typeof v == 'function') { |
| 234 this._animation.oncancel = (function(e) { |
| 235 e.target = this; |
| 236 v.call(this, e); |
| 237 }).bind(this); |
| 238 } else { |
| 239 this._animation.oncancel = v; |
| 240 } |
| 241 }, |
| 242 get currentTime() { |
| 243 this._updatePromises(); |
| 244 var currentTime = this._animation.currentTime; |
| 245 this._updatePromises(); |
| 246 return currentTime; |
| 247 }, |
| 248 set currentTime(v) { |
| 249 this._updatePromises(); |
| 250 this._animation.currentTime = isFinite(v) ? v : Math.sign(v) * Number.MAX_
VALUE; |
| 251 this._register(); |
| 252 this._forEachChild(function(child, offset) { |
| 253 child.currentTime = v - offset; |
| 254 }); |
| 255 this._updatePromises(); |
| 256 }, |
| 257 get startTime() { |
| 258 return this._animation.startTime; |
| 259 }, |
| 260 set startTime(v) { |
| 261 this._updatePromises(); |
| 262 this._animation.startTime = isFinite(v) ? v : Math.sign(v) * Number.MAX_VA
LUE; |
| 263 this._register(); |
| 264 this._forEachChild(function(child, offset) { |
| 265 child.startTime = v + offset; |
| 266 }); |
| 267 this._updatePromises(); |
| 268 }, |
| 269 get playbackRate() { |
| 270 return this._animation.playbackRate; |
| 271 }, |
| 272 set playbackRate(value) { |
| 273 this._updatePromises(); |
| 274 var oldCurrentTime = this.currentTime; |
| 275 this._animation.playbackRate = value; |
| 276 this._forEachChild(function(childAnimation) { |
| 277 childAnimation.playbackRate = value; |
| 278 }); |
| 279 if (oldCurrentTime !== null) { |
| 280 this.currentTime = oldCurrentTime; |
| 281 } |
| 282 this._updatePromises(); |
| 283 }, |
| 284 play: function() { |
| 285 this._updatePromises(); |
| 286 this._paused = false; |
| 287 this._animation.play(); |
| 288 if (this._timeline._animations.indexOf(this) == -1) { |
| 289 this._timeline._animations.push(this); |
| 290 } |
| 291 this._register(); |
| 292 scope.awaitStartTime(this); |
| 293 this._forEachChild(function(child) { |
| 294 var time = child.currentTime; |
| 295 child.play(); |
| 296 child.currentTime = time; |
| 297 }); |
| 298 this._updatePromises(); |
| 299 }, |
| 300 pause: function() { |
| 301 this._updatePromises(); |
| 302 if (this.currentTime) { |
| 303 this._holdTime = this.currentTime; |
| 304 } |
| 305 this._animation.pause(); |
| 306 this._register(); |
| 307 this._forEachChild(function(child) { |
| 308 child.pause(); |
| 309 }); |
| 310 this._paused = true; |
| 311 this._updatePromises(); |
| 312 }, |
| 313 finish: function() { |
| 314 this._updatePromises(); |
| 315 this._animation.finish(); |
| 316 this._register(); |
| 317 this._updatePromises(); |
| 318 }, |
| 319 cancel: function() { |
| 320 this._updatePromises(); |
| 321 this._animation.cancel(); |
| 322 this._register(); |
| 323 this._removeChildAnimations(); |
| 324 this._updatePromises(); |
| 325 }, |
| 326 reverse: function() { |
| 327 this._updatePromises(); |
| 328 var oldCurrentTime = this.currentTime; |
| 329 this._animation.reverse(); |
| 330 this._forEachChild(function(childAnimation) { |
| 331 childAnimation.reverse(); |
| 332 }); |
| 333 if (oldCurrentTime !== null) { |
| 334 this.currentTime = oldCurrentTime; |
| 335 } |
| 336 this._updatePromises(); |
| 337 }, |
| 338 addEventListener: function(type, handler) { |
| 339 var wrapped = handler; |
| 340 if (typeof handler == 'function') { |
| 341 wrapped = (function(e) { |
| 342 e.target = this; |
| 343 handler.call(this, e); |
| 344 }).bind(this); |
| 345 handler._wrapper = wrapped; |
| 346 } |
| 347 this._animation.addEventListener(type, wrapped); |
| 348 }, |
| 349 removeEventListener: function(type, handler) { |
| 350 this._animation.removeEventListener(type, (handler && handler._wrapper) ||
handler); |
| 351 }, |
| 352 _removeChildAnimations: function() { |
| 353 while (this._childAnimations.length) |
| 354 this._childAnimations.pop().cancel(); |
| 355 }, |
| 356 _forEachChild: function(f) { |
| 357 var offset = 0; |
| 358 if (this.effect.children && this._childAnimations.length < this.effect.chi
ldren.length) |
| 359 this._constructChildAnimations(); |
| 360 this._childAnimations.forEach(function(child) { |
| 361 f.call(this, child, offset); |
| 362 if (this.effect instanceof window.SequenceEffect) |
| 363 offset += child.effect.activeDuration; |
| 364 }.bind(this)); |
| 365 |
| 366 if (this.playState == 'pending') |
| 367 return; |
| 368 var timing = this.effect._timing; |
| 369 var t = this.currentTime; |
| 370 if (t !== null) |
| 371 t = shared.calculateIterationProgress(shared.calculateActiveDuration(tim
ing), t, timing); |
| 372 if (t == null || isNaN(t)) |
| 373 this._removeChildAnimations(); |
| 374 }, |
| 375 }; |
| 376 |
| 377 window.Animation = scope.Animation; |
| 378 |
| 379 if (WEB_ANIMATIONS_TESTING) { |
| 380 testing.webAnimationsNextAnimation = scope.Animation; |
| 381 } |
| 382 |
| 383 })(webAnimationsShared, webAnimationsNext, webAnimationsTesting); |
OLD | NEW |