blob: bce00b67296cd3f6f69292311abd7c1dd035efac [file] [log] [blame]
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(shared, testing) {
var fills = 'backwards|forwards|both|none'.split('|');
var directions = 'reverse|alternate|alternate-reverse'.split('|');
function makeTiming(timingInput, forGroup) {
var timing = {
delay: 0,
endDelay: 0,
fill: forGroup ? 'both' : 'none',
iterationStart: 0,
iterations: 1,
duration: forGroup ? 'auto' : 0,
playbackRate: 1,
direction: 'normal',
easing: 'linear',
};
if (typeof timingInput == 'number' && !isNaN(timingInput)) {
timing.duration = timingInput;
} else if (timingInput !== undefined) {
Object.getOwnPropertyNames(timingInput).forEach(function(property) {
if (timingInput[property] != 'auto') {
if (typeof timing[property] == 'number' || property == 'duration') {
if (typeof timingInput[property] != 'number' || isNaN(timingInput[property])) {
return;
}
}
if ((property == 'fill') && (fills.indexOf(timingInput[property]) == -1)) {
return;
}
if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) {
return;
}
if (property == 'playbackRate' && timingInput[property] !== 1 && shared.isDeprecated('AnimationTiming.playbackRate', '2014-11-28', 'Use AnimationPlayer.playbackRate instead.')) {
return;
}
timing[property] = timingInput[property];
}
});
}
return timing;
}
function normalizeTimingInput(timingInput, forGroup) {
var timing = makeTiming(timingInput, forGroup);
timing.easing = toTimingFunction(timing.easing);
return timing;
}
function cubic(a, b, c, d) {
if (a < 0 || a > 1 || c < 0 || c > 1) {
return linear;
}
return function(x) {
var start = 0, end = 1;
while (1) {
var mid = (start + end) / 2;
function f(a, b, m) { return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m};
var xEst = f(a, c, mid);
if (Math.abs(x - xEst) < 0.001) {
return f(b, d, mid);
}
if (xEst < x) {
start = mid;
} else {
end = mid;
}
}
}
}
var Start = 1;
var Middle = 0.5;
var End = 0;
function step(count, pos) {
return function(x) {
if (x >= 1) {
return 1;
}
var stepSize = 1 / count;
x += pos * stepSize;
return x - x % stepSize;
}
}
var presets = {
'ease': cubic(0.25, 0.1, 0.25, 1),
'ease-in': cubic(0.42, 0, 1, 1),
'ease-out': cubic(0, 0, 0.58, 1),
'ease-in-out': cubic(0.42, 0, 0.58, 1),
'step-start': step(1, Start),
'step-middle': step(1, Middle),
'step-end': step(1, End)
};
var numberString = '\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*';
var cubicBezierRe = new RegExp('cubic-bezier\\(' + numberString + ',' + numberString + ',' + numberString + ',' + numberString + '\\)');
var stepRe = /steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/;
var linear = function(x) { return x; };
function toTimingFunction(easing) {
var cubicData = cubicBezierRe.exec(easing);
if (cubicData) {
return cubic.apply(this, cubicData.slice(1).map(Number));
}
var stepData = stepRe.exec(easing);
if (stepData) {
return step(Number(stepData[1]), {'start': Start, 'middle': Middle, 'end': End}[stepData[2]]);
}
var preset = presets[easing];
if (preset) {
return preset;
}
return linear;
};
function calculateActiveDuration(timing) {
return Math.abs(repeatedDuration(timing) / timing.playbackRate);
}
function repeatedDuration(timing) {
return timing.duration * timing.iterations;
}
var PhaseNone = 0;
var PhaseBefore = 1;
var PhaseAfter = 2;
var PhaseActive = 3;
function calculatePhase(activeDuration, localTime, timing) {
if (localTime == null) {
return PhaseNone;
}
if (localTime < timing.delay) {
return PhaseBefore;
}
if (localTime >= timing.delay + activeDuration) {
return PhaseAfter;
}
return PhaseActive;
}
function calculateActiveTime(activeDuration, fillMode, localTime, phase, delay) {
switch (phase) {
case PhaseBefore:
if (fillMode == 'backwards' || fillMode == 'both')
return 0;
return null;
case PhaseActive:
return localTime - delay;
case PhaseAfter:
if (fillMode == 'forwards' || fillMode == 'both')
return activeDuration;
return null;
case PhaseNone:
return null;
}
}
function calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing) {
return (timing.playbackRate < 0 ? activeTime - activeDuration : activeTime) * timing.playbackRate + startOffset;
}
function calculateIterationTime(iterationDuration, repeatedDuration, scaledActiveTime, startOffset, timing) {
if (scaledActiveTime === Infinity || scaledActiveTime === -Infinity || (scaledActiveTime - startOffset == repeatedDuration && timing.iterations && ((timing.iterations + timing.iterationStart) % 1 == 0))) {
return iterationDuration;
}
return scaledActiveTime % iterationDuration;
}
function calculateCurrentIteration(iterationDuration, iterationTime, scaledActiveTime, timing) {
if (scaledActiveTime === 0) {
return 0;
}
if (iterationTime == iterationDuration) {
return timing.iterationStart + timing.iterations - 1;
}
return Math.floor(scaledActiveTime / iterationDuration);
}
function calculateTransformedTime(currentIteration, iterationDuration, iterationTime, timing) {
var currentIterationIsOdd = currentIteration % 2 >= 1;
var currentDirectionIsForwards = timing.direction == 'normal' || timing.direction == (currentIterationIsOdd ? 'alternate-reverse' : 'alternate');
var directedTime = currentDirectionIsForwards ? iterationTime : iterationDuration - iterationTime;
var timeFraction = directedTime / iterationDuration;
return iterationDuration * timing.easing(timeFraction);
}
function calculateTimeFraction(activeDuration, localTime, timing) {
var phase = calculatePhase(activeDuration, localTime, timing);
var activeTime = calculateActiveTime(activeDuration, timing.fill, localTime, phase, timing.delay);
if (activeTime === null)
return null;
if (activeDuration === 0)
return phase === PhaseBefore ? 0 : 1;
var startOffset = timing.iterationStart * timing.duration;
var scaledActiveTime = calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing);
var iterationTime = calculateIterationTime(timing.duration, repeatedDuration(timing), scaledActiveTime, startOffset, timing);
var currentIteration = calculateCurrentIteration(timing.duration, iterationTime, scaledActiveTime, timing);
return calculateTransformedTime(currentIteration, timing.duration, iterationTime, timing) / timing.duration;
}
shared.makeTiming = makeTiming;
shared.normalizeTimingInput = normalizeTimingInput;
shared.calculateActiveDuration = calculateActiveDuration;
shared.calculateTimeFraction = calculateTimeFraction;
shared.calculatePhase = calculatePhase;
shared.toTimingFunction = toTimingFunction;
if (WEB_ANIMATIONS_TESTING) {
testing.normalizeTimingInput = normalizeTimingInput;
testing.toTimingFunction = toTimingFunction;
testing.calculateActiveDuration = calculateActiveDuration;
testing.calculatePhase = calculatePhase;
testing.PhaseNone = PhaseNone;
testing.PhaseBefore = PhaseBefore;
testing.PhaseActive = PhaseActive;
testing.PhaseAfter = PhaseAfter;
testing.calculateActiveTime = calculateActiveTime;
testing.calculateScaledActiveTime = calculateScaledActiveTime;
testing.calculateIterationTime = calculateIterationTime;
testing.calculateCurrentIteration = calculateCurrentIteration;
testing.calculateTransformedTime = calculateTransformedTime;
}
})(webAnimationsShared, webAnimationsTesting);