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, testing) { |
| 16 |
| 17 var fills = 'backwards|forwards|both|none'.split('|'); |
| 18 var directions = 'reverse|alternate|alternate-reverse'.split('|'); |
| 19 var linear = function(x) { return x; }; |
| 20 |
| 21 function cloneTimingInput(timingInput) { |
| 22 if (typeof timingInput == 'number') { |
| 23 return timingInput; |
| 24 } |
| 25 var clone = {}; |
| 26 for (var m in timingInput) { |
| 27 clone[m] = timingInput[m]; |
| 28 } |
| 29 return clone; |
| 30 } |
| 31 |
| 32 function AnimationEffectTiming() { |
| 33 this._delay = 0; |
| 34 this._endDelay = 0; |
| 35 this._fill = 'none'; |
| 36 this._iterationStart = 0; |
| 37 this._iterations = 1; |
| 38 this._duration = 0; |
| 39 this._playbackRate = 1; |
| 40 this._direction = 'normal'; |
| 41 this._easing = 'linear'; |
| 42 this._easingFunction = linear; |
| 43 } |
| 44 |
| 45 function isInvalidTimingDeprecated() { |
| 46 return shared.isDeprecated('Invalid timing inputs', '2016-03-02', 'TypeError
exceptions will be thrown instead.', true); |
| 47 } |
| 48 |
| 49 AnimationEffectTiming.prototype = { |
| 50 _setMember: function(member, value) { |
| 51 this['_' + member] = value; |
| 52 if (this._effect) { |
| 53 this._effect._timingInput[member] = value; |
| 54 this._effect._timing = shared.normalizeTimingInput(this._effect._timingI
nput); |
| 55 this._effect.activeDuration = shared.calculateActiveDuration(this._effec
t._timing); |
| 56 if (this._effect._animation) { |
| 57 this._effect._animation._rebuildUnderlyingAnimation(); |
| 58 } |
| 59 } |
| 60 }, |
| 61 get playbackRate() { |
| 62 return this._playbackRate; |
| 63 }, |
| 64 set delay(value) { |
| 65 this._setMember('delay', value); |
| 66 }, |
| 67 get delay() { |
| 68 return this._delay; |
| 69 }, |
| 70 set endDelay(value) { |
| 71 this._setMember('endDelay', value); |
| 72 }, |
| 73 get endDelay() { |
| 74 return this._endDelay; |
| 75 }, |
| 76 set fill(value) { |
| 77 this._setMember('fill', value); |
| 78 }, |
| 79 get fill() { |
| 80 return this._fill; |
| 81 }, |
| 82 set iterationStart(value) { |
| 83 if ((isNaN(value) || value < 0) && isInvalidTimingDeprecated()) { |
| 84 throw new TypeError('iterationStart must be a non-negative number, recei
ved: ' + timing.iterationStart); |
| 85 } |
| 86 this._setMember('iterationStart', value); |
| 87 }, |
| 88 get iterationStart() { |
| 89 return this._iterationStart; |
| 90 }, |
| 91 set duration(value) { |
| 92 if (value != 'auto' && (isNaN(value) || value < 0) && isInvalidTimingDepre
cated()) { |
| 93 throw new TypeError('duration must be non-negative or auto, received: '
+ value); |
| 94 } |
| 95 this._setMember('duration', value); |
| 96 }, |
| 97 get duration() { |
| 98 return this._duration; |
| 99 }, |
| 100 set direction(value) { |
| 101 this._setMember('direction', value); |
| 102 }, |
| 103 get direction() { |
| 104 return this._direction; |
| 105 }, |
| 106 set easing(value) { |
| 107 this._easingFunction = parseEasingFunction(normalizeEasing(value)); |
| 108 this._setMember('easing', value); |
| 109 }, |
| 110 get easing() { |
| 111 return this._easing; |
| 112 }, |
| 113 set iterations(value) { |
| 114 if ((isNaN(value) || value < 0) && isInvalidTimingDeprecated()) { |
| 115 throw new TypeError('iterations must be non-negative, received: ' + valu
e); |
| 116 } |
| 117 this._setMember('iterations', value); |
| 118 }, |
| 119 get iterations() { |
| 120 return this._iterations; |
| 121 } |
| 122 }; |
| 123 |
| 124 function makeTiming(timingInput, forGroup, effect) { |
| 125 var timing = new AnimationEffectTiming(); |
| 126 if (forGroup) { |
| 127 timing.fill = 'both'; |
| 128 timing.duration = 'auto'; |
| 129 } |
| 130 if (typeof timingInput == 'number' && !isNaN(timingInput)) { |
| 131 timing.duration = timingInput; |
| 132 } else if (timingInput !== undefined) { |
| 133 Object.getOwnPropertyNames(timingInput).forEach(function(property) { |
| 134 if (timingInput[property] != 'auto') { |
| 135 if (typeof timing[property] == 'number' || property == 'duration') { |
| 136 if (typeof timingInput[property] != 'number' || isNaN(timingInput[pr
operty])) { |
| 137 return; |
| 138 } |
| 139 } |
| 140 if ((property == 'fill') && (fills.indexOf(timingInput[property]) == -
1)) { |
| 141 return; |
| 142 } |
| 143 if ((property == 'direction') && (directions.indexOf(timingInput[prope
rty]) == -1)) { |
| 144 return; |
| 145 } |
| 146 if (property == 'playbackRate' && timingInput[property] !== 1 && share
d.isDeprecated('AnimationEffectTiming.playbackRate', '2014-11-28', 'Use Animatio
n.playbackRate instead.')) { |
| 147 return; |
| 148 } |
| 149 timing[property] = timingInput[property]; |
| 150 } |
| 151 }); |
| 152 } |
| 153 return timing; |
| 154 } |
| 155 |
| 156 function numericTimingToObject(timingInput) { |
| 157 if (typeof timingInput == 'number') { |
| 158 if (isNaN(timingInput)) { |
| 159 timingInput = { duration: 0 }; |
| 160 } else { |
| 161 timingInput = { duration: timingInput }; |
| 162 } |
| 163 } |
| 164 return timingInput; |
| 165 } |
| 166 |
| 167 function normalizeTimingInput(timingInput, forGroup) { |
| 168 timingInput = shared.numericTimingToObject(timingInput); |
| 169 return makeTiming(timingInput, forGroup); |
| 170 } |
| 171 |
| 172 function cubic(a, b, c, d) { |
| 173 if (a < 0 || a > 1 || c < 0 || c > 1) { |
| 174 return linear; |
| 175 } |
| 176 return function(x) { |
| 177 if (x <= 0) { |
| 178 var start_gradient = 0; |
| 179 if (a > 0) |
| 180 start_gradient = b / a; |
| 181 else if (!b && c > 0) |
| 182 start_gradient = d / c; |
| 183 return start_gradient * x; |
| 184 } |
| 185 if (x >= 1) { |
| 186 var end_gradient = 0; |
| 187 if (c < 1) |
| 188 end_gradient = (d - 1) / (c - 1); |
| 189 else if (c == 1 && a < 1) |
| 190 end_gradient = (b - 1) / (a - 1); |
| 191 return 1 + end_gradient * (x - 1); |
| 192 } |
| 193 |
| 194 var start = 0, end = 1; |
| 195 while (start < end) { |
| 196 var mid = (start + end) / 2; |
| 197 function f(a, b, m) { return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1
- m) * m * m + m * m * m}; |
| 198 var xEst = f(a, c, mid); |
| 199 if (Math.abs(x - xEst) < 0.00001) { |
| 200 return f(b, d, mid); |
| 201 } |
| 202 if (xEst < x) { |
| 203 start = mid; |
| 204 } else { |
| 205 end = mid; |
| 206 } |
| 207 } |
| 208 return f(b, d, mid); |
| 209 } |
| 210 } |
| 211 |
| 212 var Start = 1; |
| 213 var Middle = 0.5; |
| 214 var End = 0; |
| 215 |
| 216 function step(count, pos) { |
| 217 return function(x) { |
| 218 if (x >= 1) { |
| 219 return 1; |
| 220 } |
| 221 var stepSize = 1 / count; |
| 222 x += pos * stepSize; |
| 223 return x - x % stepSize; |
| 224 } |
| 225 } |
| 226 |
| 227 var presets = { |
| 228 'ease': cubic(0.25, 0.1, 0.25, 1), |
| 229 'ease-in': cubic(0.42, 0, 1, 1), |
| 230 'ease-out': cubic(0, 0, 0.58, 1), |
| 231 'ease-in-out': cubic(0.42, 0, 0.58, 1), |
| 232 'step-start': step(1, Start), |
| 233 'step-middle': step(1, Middle), |
| 234 'step-end': step(1, End) |
| 235 }; |
| 236 |
| 237 var styleForCleaning = null; |
| 238 var numberString = '\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*'; |
| 239 var cubicBezierRe = new RegExp('cubic-bezier\\(' + numberString + ',' + number
String + ',' + numberString + ',' + numberString + '\\)'); |
| 240 var stepRe = /steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/; |
| 241 |
| 242 function normalizeEasing(easing) { |
| 243 if (!styleForCleaning) { |
| 244 styleForCleaning = document.createElement('div').style; |
| 245 } |
| 246 styleForCleaning.animationTimingFunction = ''; |
| 247 styleForCleaning.animationTimingFunction = easing; |
| 248 var normalizedEasing = styleForCleaning.animationTimingFunction; |
| 249 if (normalizedEasing == '' && isInvalidTimingDeprecated()) { |
| 250 throw new TypeError(easing + ' is not a valid value for easing'); |
| 251 } |
| 252 return normalizedEasing; |
| 253 } |
| 254 |
| 255 function parseEasingFunction(normalizedEasing) { |
| 256 if (normalizedEasing == 'linear') { |
| 257 return linear; |
| 258 } |
| 259 var cubicData = cubicBezierRe.exec(normalizedEasing); |
| 260 if (cubicData) { |
| 261 return cubic.apply(this, cubicData.slice(1).map(Number)); |
| 262 } |
| 263 var stepData = stepRe.exec(normalizedEasing); |
| 264 if (stepData) { |
| 265 return step(Number(stepData[1]), {'start': Start, 'middle': Middle, 'end':
End}[stepData[2]]); |
| 266 } |
| 267 var preset = presets[normalizedEasing]; |
| 268 if (preset) { |
| 269 return preset; |
| 270 } |
| 271 // At this point none of our parse attempts succeeded; the easing is invalid
. |
| 272 // Fall back to linear in the interest of not crashing the page. |
| 273 return linear; |
| 274 } |
| 275 |
| 276 function calculateActiveDuration(timing) { |
| 277 return Math.abs(repeatedDuration(timing) / timing.playbackRate); |
| 278 } |
| 279 |
| 280 function repeatedDuration(timing) { |
| 281 // https://w3c.github.io/web-animations/#calculating-the-active-duration |
| 282 if (timing.duration === 0 || timing.iterations === 0) { |
| 283 return 0; |
| 284 } |
| 285 return timing.duration * timing.iterations; |
| 286 } |
| 287 |
| 288 var PhaseNone = 0; |
| 289 var PhaseBefore = 1; |
| 290 var PhaseAfter = 2; |
| 291 var PhaseActive = 3; |
| 292 |
| 293 function calculatePhase(activeDuration, localTime, timing) { |
| 294 // https://w3c.github.io/web-animations/#animation-effect-phases-and-states |
| 295 if (localTime == null) { |
| 296 return PhaseNone; |
| 297 } |
| 298 |
| 299 var endTime = timing.delay + activeDuration + timing.endDelay; |
| 300 if (localTime < Math.min(timing.delay, endTime)) { |
| 301 return PhaseBefore; |
| 302 } |
| 303 if (localTime >= Math.min(timing.delay + activeDuration, endTime)) { |
| 304 return PhaseAfter; |
| 305 } |
| 306 |
| 307 return PhaseActive; |
| 308 } |
| 309 |
| 310 function calculateActiveTime(activeDuration, fillMode, localTime, phase, delay
) { |
| 311 // https://w3c.github.io/web-animations/#calculating-the-active-time |
| 312 switch (phase) { |
| 313 case PhaseBefore: |
| 314 if (fillMode == 'backwards' || fillMode == 'both') |
| 315 return 0; |
| 316 return null; |
| 317 case PhaseActive: |
| 318 return localTime - delay; |
| 319 case PhaseAfter: |
| 320 if (fillMode == 'forwards' || fillMode == 'both') |
| 321 return activeDuration; |
| 322 return null; |
| 323 case PhaseNone: |
| 324 return null; |
| 325 } |
| 326 } |
| 327 |
| 328 function calculateOverallProgress(iterationDuration, phase, iterations, active
Time, iterationStart) { |
| 329 // https://w3c.github.io/web-animations/#calculating-the-overall-progress |
| 330 var overallProgress = iterationStart; |
| 331 if (iterationDuration === 0) { |
| 332 if (phase !== PhaseBefore) { |
| 333 overallProgress += iterations; |
| 334 } |
| 335 } else { |
| 336 overallProgress += activeTime / iterationDuration; |
| 337 } |
| 338 return overallProgress; |
| 339 } |
| 340 |
| 341 function calculateSimpleIterationProgress(overallProgress, iterationStart, pha
se, iterations, activeTime, iterationDuration) { |
| 342 // https://w3c.github.io/web-animations/#calculating-the-simple-iteration-pr
ogress |
| 343 |
| 344 var simpleIterationProgress = (overallProgress === Infinity) ? iterationStar
t % 1 : overallProgress % 1; |
| 345 if (simpleIterationProgress === 0 && phase === PhaseAfter && iterations !==
0 && |
| 346 (activeTime !== 0 || iterationDuration === 0)) { |
| 347 simpleIterationProgress = 1; |
| 348 } |
| 349 return simpleIterationProgress; |
| 350 } |
| 351 |
| 352 function calculateCurrentIteration(phase, iterations, simpleIterationProgress,
overallProgress) { |
| 353 // https://w3c.github.io/web-animations/#calculating-the-current-iteration |
| 354 if (phase === PhaseAfter && iterations === Infinity) { |
| 355 return Infinity; |
| 356 } |
| 357 if (simpleIterationProgress === 1) { |
| 358 return Math.floor(overallProgress) - 1; |
| 359 } |
| 360 return Math.floor(overallProgress); |
| 361 } |
| 362 |
| 363 function calculateDirectedProgress(playbackDirection, currentIteration, simple
IterationProgress) { |
| 364 // https://w3c.github.io/web-animations/#calculating-the-directed-progress |
| 365 var currentDirection = playbackDirection; |
| 366 if (playbackDirection !== 'normal' && playbackDirection !== 'reverse') { |
| 367 var d = currentIteration; |
| 368 if (playbackDirection === 'alternate-reverse') { |
| 369 d += 1; |
| 370 } |
| 371 currentDirection = 'normal'; |
| 372 if (d !== Infinity && d % 2 !== 0) { |
| 373 currentDirection = 'reverse'; |
| 374 } |
| 375 } |
| 376 if (currentDirection === 'normal') { |
| 377 return simpleIterationProgress; |
| 378 } |
| 379 return 1 - simpleIterationProgress; |
| 380 } |
| 381 |
| 382 function calculateIterationProgress(activeDuration, localTime, timing) { |
| 383 var phase = calculatePhase(activeDuration, localTime, timing); |
| 384 var activeTime = calculateActiveTime(activeDuration, timing.fill, localTime,
phase, timing.delay); |
| 385 if (activeTime === null) |
| 386 return null; |
| 387 |
| 388 var overallProgress = calculateOverallProgress(timing.duration, phase, timin
g.iterations, activeTime, timing.iterationStart); |
| 389 var simpleIterationProgress = calculateSimpleIterationProgress(overallProgre
ss, timing.iterationStart, phase, timing.iterations, activeTime, timing.duration
); |
| 390 var currentIteration = calculateCurrentIteration(phase, timing.iterations, s
impleIterationProgress, overallProgress); |
| 391 var directedProgress = calculateDirectedProgress(timing.direction, currentIt
eration, simpleIterationProgress); |
| 392 |
| 393 // https://w3c.github.io/web-animations/#calculating-the-transformed-progres
s |
| 394 // https://w3c.github.io/web-animations/#calculating-the-iteration-progress |
| 395 return timing._easingFunction(directedProgress); |
| 396 } |
| 397 |
| 398 shared.cloneTimingInput = cloneTimingInput; |
| 399 shared.makeTiming = makeTiming; |
| 400 shared.numericTimingToObject = numericTimingToObject; |
| 401 shared.normalizeTimingInput = normalizeTimingInput; |
| 402 shared.calculateActiveDuration = calculateActiveDuration; |
| 403 shared.calculateIterationProgress = calculateIterationProgress; |
| 404 shared.calculatePhase = calculatePhase; |
| 405 shared.normalizeEasing = normalizeEasing; |
| 406 shared.parseEasingFunction = parseEasingFunction; |
| 407 |
| 408 if (WEB_ANIMATIONS_TESTING) { |
| 409 testing.normalizeTimingInput = normalizeTimingInput; |
| 410 testing.normalizeEasing = normalizeEasing; |
| 411 testing.parseEasingFunction = parseEasingFunction; |
| 412 testing.calculateActiveDuration = calculateActiveDuration; |
| 413 testing.calculatePhase = calculatePhase; |
| 414 testing.PhaseNone = PhaseNone; |
| 415 testing.PhaseBefore = PhaseBefore; |
| 416 testing.PhaseActive = PhaseActive; |
| 417 testing.PhaseAfter = PhaseAfter; |
| 418 } |
| 419 |
| 420 })(webAnimationsShared, webAnimationsTesting); |
OLD | NEW |