1 // Copyright 2014 Google Inc. All rights reserved.
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
15 (function(shared, testing) {
17 var fills = 'backwards|forwards|both'.split('|');
18 var directions = 'reverse|alternate|alternate-reverse'.split('|');
20 function makeTiming(timingInput, forGroup) {
24 fill: forGroup ? 'both' : 'none',
27 duration: forGroup ? 'auto' : 0,
32 if (typeof timingInput == 'number' && !isNaN(timingInput)) {
33 timing.duration = timingInput;
34 } else if (timingInput !== undefined) {
35 Object.getOwnPropertyNames(timingInput).forEach(function(property) {
36 if (timingInput[property] != 'auto') {
37 if (typeof timing[property] == 'number' || property == 'duration') {
38 if (typeof timingInput[property] != 'number' || isNaN(timingInput[property])) {
42 if ((property == 'fill') && (fills.indexOf(timingInput[property]) == -1)) {
45 if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) {
48 if (property == 'playbackRate' && shared.isDeprecated('AnimationTiming.playbackRate', '2014-11-28', 'Use AnimationPlayer.playbackRate instead.')) {
51 timing[property] = timingInput[property];
58 function normalizeTimingInput(timingInput, forGroup) {
59 var timing = makeTiming(timingInput, forGroup);
60 timing.easing = toTimingFunction(timing.easing);
64 function cubic(a, b, c, d) {
65 if (a < 0 || a > 1 || c < 0 || c > 1) {
69 var start = 0, end = 1;
71 var mid = (start + end) / 2;
72 function f(a, b, m) { return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m};
73 var xEst = f(a, c, mid);
74 if (Math.abs(x - xEst) < 0.001) {
90 function step(count, pos) {
95 var stepSize = 1 / count;
97 return x - x % stepSize;
102 'ease': cubic(0.25, 0.1, 0.25, 1),
103 'ease-in': cubic(0.42, 0, 1, 1),
104 'ease-out': cubic(0, 0, 0.58, 1),
105 'ease-in-out': cubic(0.42, 0, 0.58, 1),
106 'step-start': step(1, Start),
107 'step-middle': step(1, Middle),
108 'step-end': step(1, End)
111 var numberString = '\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*';
112 var cubicBezierRe = new RegExp('cubic-bezier\\(' + numberString + ',' + numberString + ',' + numberString + ',' + numberString + '\\)');
113 var stepRe = /steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/;
114 var linear = function(x) { return x; };
116 function toTimingFunction(easing) {
117 var cubicData = cubicBezierRe.exec(easing);
119 return cubic.apply(this, cubicData.slice(1).map(Number));
121 var stepData = stepRe.exec(easing);
123 return step(Number(stepData[1]), {'start': Start, 'middle': Middle, 'end': End}[stepData[2]]);
125 var preset = presets[easing];
132 function calculateActiveDuration(timing) {
133 return Math.abs(repeatedDuration(timing) / timing.playbackRate);
136 function repeatedDuration(timing) {
137 return timing.duration * timing.iterations;
145 function calculatePhase(activeDuration, localTime, timing) {
146 if (localTime == null) {
149 if (localTime < timing.delay) {
152 if (localTime >= timing.delay + activeDuration) {
158 function calculateActiveTime(activeDuration, fillMode, localTime, phase, delay) {
161 if (fillMode == 'backwards' || fillMode == 'both')
165 return localTime - delay;
167 if (fillMode == 'forwards' || fillMode == 'both')
168 return activeDuration;
175 function calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing) {
176 return (timing.playbackRate < 0 ? activeTime - activeDuration : activeTime) * timing.playbackRate + startOffset;
179 function calculateIterationTime(iterationDuration, repeatedDuration, scaledActiveTime, startOffset, timing) {
180 if (scaledActiveTime === Infinity || scaledActiveTime === -Infinity || (scaledActiveTime - startOffset == repeatedDuration && timing.iterations && ((timing.iterations + timing.iterationStart) % 1 == 0))) {
181 return iterationDuration;
184 return scaledActiveTime % iterationDuration;
187 function calculateCurrentIteration(iterationDuration, iterationTime, scaledActiveTime, timing) {
188 if (scaledActiveTime === 0) {
191 if (iterationTime == iterationDuration) {
192 return timing.iterationStart + timing.iterations - 1;
194 return Math.floor(scaledActiveTime / iterationDuration);
197 function calculateTransformedTime(currentIteration, iterationDuration, iterationTime, timing) {
198 var currentIterationIsOdd = currentIteration % 2 >= 1;
199 var currentDirectionIsForwards = timing.direction == 'normal' || timing.direction == (currentIterationIsOdd ? 'alternate-reverse' : 'alternate');
200 var directedTime = currentDirectionIsForwards ? iterationTime : iterationDuration - iterationTime;
201 var timeFraction = directedTime / iterationDuration;
202 return iterationDuration * timing.easing(timeFraction);
205 function calculateTimeFraction(activeDuration, localTime, timing) {
206 var phase = calculatePhase(activeDuration, localTime, timing);
207 var activeTime = calculateActiveTime(activeDuration, timing.fill, localTime, phase, timing.delay);
208 if (activeTime === null)
210 if (activeDuration === 0)
211 return phase === PhaseBefore ? 0 : 1;
212 var startOffset = timing.iterationStart * timing.duration;
213 var scaledActiveTime = calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing);
214 var iterationTime = calculateIterationTime(timing.duration, repeatedDuration(timing), scaledActiveTime, startOffset, timing);
215 var currentIteration = calculateCurrentIteration(timing.duration, iterationTime, scaledActiveTime, timing);
216 return calculateTransformedTime(currentIteration, timing.duration, iterationTime, timing) / timing.duration;
219 shared.makeTiming = makeTiming;
220 shared.normalizeTimingInput = normalizeTimingInput;
221 shared.calculateActiveDuration = calculateActiveDuration;
222 shared.calculateTimeFraction = calculateTimeFraction;
223 shared.calculatePhase = calculatePhase;
224 shared.toTimingFunction = toTimingFunction;
226 if (WEB_ANIMATIONS_TESTING) {
227 testing.normalizeTimingInput = normalizeTimingInput;
228 testing.toTimingFunction = toTimingFunction;
229 testing.calculateActiveDuration = calculateActiveDuration;
230 testing.calculatePhase = calculatePhase;
231 testing.PhaseNone = PhaseNone;
232 testing.PhaseBefore = PhaseBefore;
233 testing.PhaseActive = PhaseActive;
234 testing.PhaseAfter = PhaseAfter;
235 testing.calculateActiveTime = calculateActiveTime;
236 testing.calculateScaledActiveTime = calculateScaledActiveTime;
237 testing.calculateIterationTime = calculateIterationTime;
238 testing.calculateCurrentIteration = calculateCurrentIteration;
239 testing.calculateTransformedTime = calculateTransformedTime;
242 })(webAnimationsShared, webAnimationsTesting);