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. 14 15(function(shared, testing) { 16 17 var fills = 'backwards|forwards|both|none'.split('|'); 18 var directions = 'reverse|alternate|alternate-reverse'.split('|'); 19 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' && timingInput[property] !== 1 && 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 } 57 58 function normalizeTimingInput(timingInput, forGroup) { 59 var timing = makeTiming(timingInput, forGroup); 60 timing.easing = toTimingFunction(timing.easing); 61 return timing; 62 } 63 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 } 85 86 var Start = 1; 87 var Middle = 0.5; 88 var End = 0; 89 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 } 100 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 }; 110 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; }; 115 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 }; 131 132 function calculateActiveDuration(timing) { 133 return Math.abs(repeatedDuration(timing) / timing.playbackRate); 134 } 135 136 function repeatedDuration(timing) { 137 return timing.duration * timing.iterations; 138 } 139 140 var PhaseNone = 0; 141 var PhaseBefore = 1; 142 var PhaseAfter = 2; 143 var PhaseActive = 3; 144 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 } 157 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 } 174 175 function calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing) { 176 return (timing.playbackRate < 0 ? activeTime - activeDuration : activeTime) * timing.playbackRate + startOffset; 177 } 178 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 } 183 184 return scaledActiveTime % iterationDuration; 185 } 186 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 } 196 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 } 204 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 } 218 219 shared.makeTiming = makeTiming; 220 shared.normalizeTimingInput = normalizeTimingInput; 221 shared.calculateActiveDuration = calculateActiveDuration; 222 shared.calculateTimeFraction = calculateTimeFraction; 223 shared.calculatePhase = calculatePhase; 224 shared.toTimingFunction = toTimingFunction; 225 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 } 241 242})(webAnimationsShared, webAnimationsTesting); 243