Revert of Roll src/third_party/WebKit e0eac24:489c548 (svn 193311:193320) (patchset...
[chromium-blink-merge.git] / third_party / web-animations-js / sources / src / timing-utilities.js
blob00da45b96c9bd722e4dfeb52f02d24a54d779115
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.
15 (function(shared, testing) {
17   var fills = 'backwards|forwards|both'.split('|');
18   var directions = 'reverse|alternate|alternate-reverse'.split('|');
20   function makeTiming(timingInput, forGroup) {
21     var timing = {
22       delay: 0,
23       endDelay: 0,
24       fill: forGroup ? 'both' : 'none',
25       iterationStart: 0,
26       iterations: 1,
27       duration: forGroup ? 'auto' : 0,
28       playbackRate: 1,
29       direction: 'normal',
30       easing: 'linear',
31     };
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])) {
39               return;
40             }
41           }
42           if ((property == 'fill') && (fills.indexOf(timingInput[property]) == -1)) {
43             return;
44           }
45           if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) {
46             return;
47           }
48           if (property == 'playbackRate' && shared.isDeprecated('AnimationTiming.playbackRate', '2014-11-28', 'Use AnimationPlayer.playbackRate instead.')) {
49             return;
50           }
51           timing[property] = timingInput[property];
52         }
53       });
54     }
55     return timing;
56   }
58   function normalizeTimingInput(timingInput, forGroup) {
59     var timing = makeTiming(timingInput, forGroup);
60     timing.easing = toTimingFunction(timing.easing);
61     return timing;
62   }
64   function cubic(a, b, c, d) {
65     if (a < 0 || a > 1 || c < 0 || c > 1) {
66       return linear;
67     }
68     return function(x) {
69       var start = 0, end = 1;
70       while (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) {
75           return f(b, d, mid);
76         }
77         if (xEst < x) {
78           start = mid;
79         } else {
80           end = mid;
81         }
82       }
83     }
84   }
86   var Start = 1;
87   var Middle = 0.5;
88   var End = 0;
90   function step(count, pos) {
91     return function(x) {
92       if (x >= 1) {
93         return 1;
94       }
95       var stepSize = 1 / count;
96       x += pos * stepSize;
97       return x - x % stepSize;
98     }
99   }
101   var presets = {
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)
109   };
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);
118     if (cubicData) {
119       return cubic.apply(this, cubicData.slice(1).map(Number));
120     }
121     var stepData = stepRe.exec(easing);
122     if (stepData) {
123       return step(Number(stepData[1]), {'start': Start, 'middle': Middle, 'end': End}[stepData[2]]);
124     }
125     var preset = presets[easing];
126     if (preset) {
127       return preset;
128     }
129     return linear;
130   };
132   function calculateActiveDuration(timing) {
133     return Math.abs(repeatedDuration(timing) / timing.playbackRate);
134   }
136   function repeatedDuration(timing) {
137     return timing.duration * timing.iterations;
138   }
140   var PhaseNone = 0;
141   var PhaseBefore = 1;
142   var PhaseAfter = 2;
143   var PhaseActive = 3;
145   function calculatePhase(activeDuration, localTime, timing) {
146     if (localTime == null) {
147       return PhaseNone;
148     }
149     if (localTime < timing.delay) {
150       return PhaseBefore;
151     }
152     if (localTime >= timing.delay + activeDuration) {
153       return PhaseAfter;
154     }
155     return PhaseActive;
156   }
158   function calculateActiveTime(activeDuration, fillMode, localTime, phase, delay) {
159     switch (phase) {
160       case PhaseBefore:
161         if (fillMode == 'backwards' || fillMode == 'both')
162           return 0;
163         return null;
164       case PhaseActive:
165         return localTime - delay;
166       case PhaseAfter:
167         if (fillMode == 'forwards' || fillMode == 'both')
168           return activeDuration;
169         return null;
170       case PhaseNone:
171         return null;
172     }
173   }
175   function calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing) {
176     return (timing.playbackRate < 0 ? activeTime - activeDuration : activeTime) * timing.playbackRate + startOffset;
177   }
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;
182     }
184     return scaledActiveTime % iterationDuration;
185   }
187   function calculateCurrentIteration(iterationDuration, iterationTime, scaledActiveTime, timing) {
188     if (scaledActiveTime === 0) {
189       return 0;
190     }
191     if (iterationTime == iterationDuration) {
192       return timing.iterationStart + timing.iterations - 1;
193     }
194     return Math.floor(scaledActiveTime / iterationDuration);
195   }
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);
203   }
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)
209       return 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;
217   }
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;
240   }
242 })(webAnimationsShared, webAnimationsTesting);