// 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);