1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
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._timingInput);
|
55 | this._effect.activeDuration = shared.calculateActiveDuration(this._effect._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, received: ' + value);
|
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) && isInvalidTimingDeprecated()) {
|
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: ' + value);
|
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[property])) {
|
137 | return;
|
138 | }
|
139 | }
|
140 | if ((property == 'fill') && (fills.indexOf(timingInput[property]) == -1)) {
|
141 | return;
|
142 | }
|
143 | if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) {
|
144 | return;
|
145 | }
|
146 | if (property == 'playbackRate' && timingInput[property] !== 1 && shared.isDeprecated('AnimationEffectTiming.playbackRate', '2014-11-28', 'Use Animation.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 | function f(a, b, m) { return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m};
|
196 | while (start < end) {
|
197 | var mid = (start + end) / 2;
|
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 + ',' + numberString + ',' + numberString + ',' + numberString + '\\)');
|
240 | var step1Re = /steps\(\s*(\d+)\s*\)/;
|
241 | var step2Re = /steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/;
|
242 |
|
243 | function normalizeEasing(easing) {
|
244 | if (!styleForCleaning) {
|
245 | styleForCleaning = document.createElement('div').style;
|
246 | }
|
247 | styleForCleaning.animationTimingFunction = '';
|
248 | styleForCleaning.animationTimingFunction = easing;
|
249 | var normalizedEasing = styleForCleaning.animationTimingFunction;
|
250 | if (normalizedEasing == '' && isInvalidTimingDeprecated()) {
|
251 | throw new TypeError(easing + ' is not a valid value for easing');
|
252 | }
|
253 | return normalizedEasing;
|
254 | }
|
255 |
|
256 | function parseEasingFunction(normalizedEasing) {
|
257 | if (normalizedEasing == 'linear') {
|
258 | return linear;
|
259 | }
|
260 | var cubicData = cubicBezierRe.exec(normalizedEasing);
|
261 | if (cubicData) {
|
262 | return cubic.apply(this, cubicData.slice(1).map(Number));
|
263 | }
|
264 | var step1Data = step1Re.exec(normalizedEasing);
|
265 | if (step1Data) {
|
266 | return step(Number(step1Data[1]), End);
|
267 | }
|
268 | var step2Data = step2Re.exec(normalizedEasing);
|
269 | if (step2Data) {
|
270 | return step(Number(step2Data[1]), {'start': Start, 'middle': Middle, 'end': End}[step2Data[2]]);
|
271 | }
|
272 | var preset = presets[normalizedEasing];
|
273 | if (preset) {
|
274 | return preset;
|
275 | }
|
276 |
|
277 |
|
278 | return linear;
|
279 | }
|
280 |
|
281 | function calculateActiveDuration(timing) {
|
282 | return Math.abs(repeatedDuration(timing) / timing.playbackRate);
|
283 | }
|
284 |
|
285 | function repeatedDuration(timing) {
|
286 |
|
287 | if (timing.duration === 0 || timing.iterations === 0) {
|
288 | return 0;
|
289 | }
|
290 | return timing.duration * timing.iterations;
|
291 | }
|
292 |
|
293 | var PhaseNone = 0;
|
294 | var PhaseBefore = 1;
|
295 | var PhaseAfter = 2;
|
296 | var PhaseActive = 3;
|
297 |
|
298 | function calculatePhase(activeDuration, localTime, timing) {
|
299 |
|
300 | if (localTime == null) {
|
301 | return PhaseNone;
|
302 | }
|
303 |
|
304 | var endTime = timing.delay + activeDuration + timing.endDelay;
|
305 | if (localTime < Math.min(timing.delay, endTime)) {
|
306 | return PhaseBefore;
|
307 | }
|
308 | if (localTime >= Math.min(timing.delay + activeDuration, endTime)) {
|
309 | return PhaseAfter;
|
310 | }
|
311 |
|
312 | return PhaseActive;
|
313 | }
|
314 |
|
315 | function calculateActiveTime(activeDuration, fillMode, localTime, phase, delay) {
|
316 |
|
317 | switch (phase) {
|
318 | case PhaseBefore:
|
319 | if (fillMode == 'backwards' || fillMode == 'both')
|
320 | return 0;
|
321 | return null;
|
322 | case PhaseActive:
|
323 | return localTime - delay;
|
324 | case PhaseAfter:
|
325 | if (fillMode == 'forwards' || fillMode == 'both')
|
326 | return activeDuration;
|
327 | return null;
|
328 | case PhaseNone:
|
329 | return null;
|
330 | }
|
331 | }
|
332 |
|
333 | function calculateOverallProgress(iterationDuration, phase, iterations, activeTime, iterationStart) {
|
334 |
|
335 | var overallProgress = iterationStart;
|
336 | if (iterationDuration === 0) {
|
337 | if (phase !== PhaseBefore) {
|
338 | overallProgress += iterations;
|
339 | }
|
340 | } else {
|
341 | overallProgress += activeTime / iterationDuration;
|
342 | }
|
343 | return overallProgress;
|
344 | }
|
345 |
|
346 | function calculateSimpleIterationProgress(overallProgress, iterationStart, phase, iterations, activeTime, iterationDuration) {
|
347 |
|
348 |
|
349 | var simpleIterationProgress = (overallProgress === Infinity) ? iterationStart % 1 : overallProgress % 1;
|
350 | if (simpleIterationProgress === 0 && phase === PhaseAfter && iterations !== 0 &&
|
351 | (activeTime !== 0 || iterationDuration === 0)) {
|
352 | simpleIterationProgress = 1;
|
353 | }
|
354 | return simpleIterationProgress;
|
355 | }
|
356 |
|
357 | function calculateCurrentIteration(phase, iterations, simpleIterationProgress, overallProgress) {
|
358 |
|
359 | if (phase === PhaseAfter && iterations === Infinity) {
|
360 | return Infinity;
|
361 | }
|
362 | if (simpleIterationProgress === 1) {
|
363 | return Math.floor(overallProgress) - 1;
|
364 | }
|
365 | return Math.floor(overallProgress);
|
366 | }
|
367 |
|
368 | function calculateDirectedProgress(playbackDirection, currentIteration, simpleIterationProgress) {
|
369 |
|
370 | var currentDirection = playbackDirection;
|
371 | if (playbackDirection !== 'normal' && playbackDirection !== 'reverse') {
|
372 | var d = currentIteration;
|
373 | if (playbackDirection === 'alternate-reverse') {
|
374 | d += 1;
|
375 | }
|
376 | currentDirection = 'normal';
|
377 | if (d !== Infinity && d % 2 !== 0) {
|
378 | currentDirection = 'reverse';
|
379 | }
|
380 | }
|
381 | if (currentDirection === 'normal') {
|
382 | return simpleIterationProgress;
|
383 | }
|
384 | return 1 - simpleIterationProgress;
|
385 | }
|
386 |
|
387 | function calculateIterationProgress(activeDuration, localTime, timing) {
|
388 | var phase = calculatePhase(activeDuration, localTime, timing);
|
389 | var activeTime = calculateActiveTime(activeDuration, timing.fill, localTime, phase, timing.delay);
|
390 | if (activeTime === null)
|
391 | return null;
|
392 |
|
393 | var overallProgress = calculateOverallProgress(timing.duration, phase, timing.iterations, activeTime, timing.iterationStart);
|
394 | var simpleIterationProgress = calculateSimpleIterationProgress(overallProgress, timing.iterationStart, phase, timing.iterations, activeTime, timing.duration);
|
395 | var currentIteration = calculateCurrentIteration(phase, timing.iterations, simpleIterationProgress, overallProgress);
|
396 | var directedProgress = calculateDirectedProgress(timing.direction, currentIteration, simpleIterationProgress);
|
397 |
|
398 |
|
399 |
|
400 | return timing._easingFunction(directedProgress);
|
401 | }
|
402 |
|
403 | shared.cloneTimingInput = cloneTimingInput;
|
404 | shared.makeTiming = makeTiming;
|
405 | shared.numericTimingToObject = numericTimingToObject;
|
406 | shared.normalizeTimingInput = normalizeTimingInput;
|
407 | shared.calculateActiveDuration = calculateActiveDuration;
|
408 | shared.calculateIterationProgress = calculateIterationProgress;
|
409 | shared.calculatePhase = calculatePhase;
|
410 | shared.normalizeEasing = normalizeEasing;
|
411 | shared.parseEasingFunction = parseEasingFunction;
|
412 |
|
413 | if (WEB_ANIMATIONS_TESTING) {
|
414 | testing.normalizeTimingInput = normalizeTimingInput;
|
415 | testing.normalizeEasing = normalizeEasing;
|
416 | testing.parseEasingFunction = parseEasingFunction;
|
417 | testing.calculateActiveDuration = calculateActiveDuration;
|
418 | testing.calculatePhase = calculatePhase;
|
419 | testing.PhaseNone = PhaseNone;
|
420 | testing.PhaseBefore = PhaseBefore;
|
421 | testing.PhaseActive = PhaseActive;
|
422 | testing.PhaseAfter = PhaseAfter;
|
423 | }
|
424 |
|
425 | })(webAnimationsShared, webAnimationsTesting);
|