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 |
| 17 shared.sequenceNumber = 0; |
| 18 |
| 19 var AnimationEvent = function(target, currentTime, timelineTime) { |
| 20 this.target = target; |
| 21 this.currentTime = currentTime; |
| 22 this.timelineTime = timelineTime; |
| 23 |
| 24 this.type = 'finish'; |
| 25 this.bubbles = false; |
| 26 this.cancelable = false; |
| 27 this.currentTarget = target; |
| 28 this.defaultPrevented = false; |
| 29 this.eventPhase = Event.AT_TARGET; |
| 30 this.timeStamp = Date.now(); |
| 31 }; |
| 32 |
| 33 scope.Animation = function(effect) { |
| 34 this.id = ''; |
| 35 if (effect && effect._id) { |
| 36 this.id = effect._id; |
| 37 } |
| 38 this._sequenceNumber = shared.sequenceNumber++; |
| 39 this._currentTime = 0; |
| 40 this._startTime = null; |
| 41 this._paused = false; |
| 42 this._playbackRate = 1; |
| 43 this._inTimeline = true; |
| 44 this._finishedFlag = true; |
| 45 this.onfinish = null; |
| 46 this._finishHandlers = []; |
| 47 this._effect = effect; |
| 48 this._inEffect = this._effect._update(0); |
| 49 this._idle = true; |
| 50 this._currentTimePending = false; |
| 51 }; |
| 52 |
| 53 scope.Animation.prototype = { |
| 54 _ensureAlive: function() { |
| 55 // If an animation is playing backwards and is not fill backwards/both |
| 56 // then it should go out of effect when it reaches the start of its |
| 57 // active interval (currentTime == 0). |
| 58 if (this.playbackRate < 0 && this.currentTime === 0) { |
| 59 this._inEffect = this._effect._update(-1); |
| 60 } else { |
| 61 this._inEffect = this._effect._update(this.currentTime); |
| 62 } |
| 63 if (!this._inTimeline && (this._inEffect || !this._finishedFlag)) { |
| 64 this._inTimeline = true; |
| 65 scope.timeline._animations.push(this); |
| 66 } |
| 67 }, |
| 68 _tickCurrentTime: function(newTime, ignoreLimit) { |
| 69 if (newTime != this._currentTime) { |
| 70 this._currentTime = newTime; |
| 71 if (this._isFinished && !ignoreLimit) |
| 72 this._currentTime = this._playbackRate > 0 ? this._totalDuration : 0; |
| 73 this._ensureAlive(); |
| 74 } |
| 75 }, |
| 76 get currentTime() { |
| 77 if (this._idle || this._currentTimePending) |
| 78 return null; |
| 79 return this._currentTime; |
| 80 }, |
| 81 set currentTime(newTime) { |
| 82 newTime = +newTime; |
| 83 if (isNaN(newTime)) |
| 84 return; |
| 85 scope.restart(); |
| 86 if (!this._paused && this._startTime != null) { |
| 87 this._startTime = this._timeline.currentTime - newTime / this._playbackR
ate; |
| 88 } |
| 89 this._currentTimePending = false; |
| 90 if (this._currentTime == newTime) |
| 91 return; |
| 92 if (this._idle) { |
| 93 this._idle = false; |
| 94 this._paused = true; |
| 95 } |
| 96 this._tickCurrentTime(newTime, true); |
| 97 scope.applyDirtiedAnimation(this); |
| 98 }, |
| 99 get startTime() { |
| 100 return this._startTime; |
| 101 }, |
| 102 set startTime(newTime) { |
| 103 newTime = +newTime; |
| 104 if (isNaN(newTime)) |
| 105 return; |
| 106 if (this._paused || this._idle) |
| 107 return; |
| 108 this._startTime = newTime; |
| 109 this._tickCurrentTime((this._timeline.currentTime - this._startTime) * thi
s.playbackRate); |
| 110 scope.applyDirtiedAnimation(this); |
| 111 }, |
| 112 get playbackRate() { |
| 113 return this._playbackRate; |
| 114 }, |
| 115 set playbackRate(value) { |
| 116 if (value == this._playbackRate) { |
| 117 return; |
| 118 } |
| 119 var oldCurrentTime = this.currentTime; |
| 120 this._playbackRate = value; |
| 121 this._startTime = null; |
| 122 if (this.playState != 'paused' && this.playState != 'idle') { |
| 123 this._finishedFlag = false; |
| 124 this._idle = false; |
| 125 this._ensureAlive(); |
| 126 scope.applyDirtiedAnimation(this); |
| 127 } |
| 128 if (oldCurrentTime != null) { |
| 129 this.currentTime = oldCurrentTime; |
| 130 } |
| 131 }, |
| 132 get _isFinished() { |
| 133 return !this._idle && (this._playbackRate > 0 && this._currentTime >= this
._totalDuration || |
| 134 this._playbackRate < 0 && this._currentTime <= 0); |
| 135 }, |
| 136 get _totalDuration() { return this._effect._totalDuration; }, |
| 137 get playState() { |
| 138 if (this._idle) |
| 139 return 'idle'; |
| 140 if ((this._startTime == null && !this._paused && this.playbackRate != 0) |
| this._currentTimePending) |
| 141 return 'pending'; |
| 142 if (this._paused) |
| 143 return 'paused'; |
| 144 if (this._isFinished) |
| 145 return 'finished'; |
| 146 return 'running'; |
| 147 }, |
| 148 _rewind: function() { |
| 149 if (this._playbackRate >= 0) { |
| 150 this._currentTime = 0; |
| 151 } else if (this._totalDuration < Infinity) { |
| 152 this._currentTime = this._totalDuration; |
| 153 } else { |
| 154 throw new DOMException( |
| 155 'Unable to rewind negative playback rate animation with infinite dur
ation', |
| 156 'InvalidStateError'); |
| 157 } |
| 158 }, |
| 159 play: function() { |
| 160 this._paused = false; |
| 161 if (this._isFinished || this._idle) { |
| 162 this._rewind(); |
| 163 this._startTime = null; |
| 164 } |
| 165 this._finishedFlag = false; |
| 166 this._idle = false; |
| 167 this._ensureAlive(); |
| 168 scope.applyDirtiedAnimation(this); |
| 169 }, |
| 170 pause: function() { |
| 171 if (!this._isFinished && !this._paused && !this._idle) { |
| 172 this._currentTimePending = true; |
| 173 } else if (this._idle) { |
| 174 this._rewind(); |
| 175 this._idle = false; |
| 176 } |
| 177 this._startTime = null; |
| 178 this._paused = true; |
| 179 }, |
| 180 finish: function() { |
| 181 if (this._idle) |
| 182 return; |
| 183 this.currentTime = this._playbackRate > 0 ? this._totalDuration : 0; |
| 184 this._startTime = this._totalDuration - this.currentTime; |
| 185 this._currentTimePending = false; |
| 186 scope.applyDirtiedAnimation(this); |
| 187 }, |
| 188 cancel: function() { |
| 189 if (!this._inEffect) |
| 190 return; |
| 191 this._inEffect = false; |
| 192 this._idle = true; |
| 193 this._paused = false; |
| 194 this._isFinished = true; |
| 195 this._finishedFlag = true; |
| 196 this._currentTime = 0; |
| 197 this._startTime = null; |
| 198 this._effect._update(null); |
| 199 // effects are invalid after cancellation as the animation state |
| 200 // needs to un-apply. |
| 201 scope.applyDirtiedAnimation(this); |
| 202 }, |
| 203 reverse: function() { |
| 204 this.playbackRate *= -1; |
| 205 this.play(); |
| 206 }, |
| 207 addEventListener: function(type, handler) { |
| 208 if (typeof handler == 'function' && type == 'finish') |
| 209 this._finishHandlers.push(handler); |
| 210 }, |
| 211 removeEventListener: function(type, handler) { |
| 212 if (type != 'finish') |
| 213 return; |
| 214 var index = this._finishHandlers.indexOf(handler); |
| 215 if (index >= 0) |
| 216 this._finishHandlers.splice(index, 1); |
| 217 }, |
| 218 _fireEvents: function(baseTime) { |
| 219 if (this._isFinished) { |
| 220 if (!this._finishedFlag) { |
| 221 var event = new AnimationEvent(this, this._currentTime, baseTime); |
| 222 var handlers = this._finishHandlers.concat(this.onfinish ? [this.onfin
ish] : []); |
| 223 setTimeout(function() { |
| 224 handlers.forEach(function(handler) { |
| 225 handler.call(event.target, event); |
| 226 }); |
| 227 }, 0); |
| 228 this._finishedFlag = true; |
| 229 } |
| 230 } else { |
| 231 this._finishedFlag = false; |
| 232 } |
| 233 }, |
| 234 _tick: function(timelineTime, isAnimationFrame) { |
| 235 if (!this._idle && !this._paused) { |
| 236 if (this._startTime == null) { |
| 237 if (isAnimationFrame) { |
| 238 this.startTime = timelineTime - this._currentTime / this.playbackRat
e; |
| 239 } |
| 240 } else if (!this._isFinished) { |
| 241 this._tickCurrentTime((timelineTime - this._startTime) * this.playback
Rate); |
| 242 } |
| 243 } |
| 244 |
| 245 if (isAnimationFrame) { |
| 246 this._currentTimePending = false; |
| 247 this._fireEvents(timelineTime); |
| 248 } |
| 249 }, |
| 250 get _needsTick() { |
| 251 return (this.playState in {'pending': 1, 'running': 1}) || !this._finished
Flag; |
| 252 }, |
| 253 _targetAnimations: function() { |
| 254 var target = this._effect._target; |
| 255 if (!target._activeAnimations) { |
| 256 target._activeAnimations = []; |
| 257 } |
| 258 return target._activeAnimations; |
| 259 }, |
| 260 _markTarget: function() { |
| 261 var animations = this._targetAnimations(); |
| 262 if (animations.indexOf(this) === -1) { |
| 263 animations.push(this); |
| 264 } |
| 265 }, |
| 266 _unmarkTarget: function() { |
| 267 var animations = this._targetAnimations(); |
| 268 var index = animations.indexOf(this); |
| 269 if (index !== -1) { |
| 270 animations.splice(index, 1); |
| 271 } |
| 272 }, |
| 273 }; |
| 274 |
| 275 if (WEB_ANIMATIONS_TESTING) { |
| 276 testing.webAnimations1Animation = scope.Animation; |
| 277 } |
| 278 |
| 279 })(webAnimationsShared, webAnimations1, webAnimationsTesting); |
OLD | NEW |