Skip to content

Commit

Permalink
Calculating the iteration progress now follows the spec
Browse files Browse the repository at this point in the history
All stages between active time and iteration progress have been re-written to match the spec.
https://w3c.github.io/web-animations/#calculating-the-iteration-progress
  • Loading branch information
ericwilligers committed Jul 29, 2016
1 parent 46f41d1 commit b85f9a3
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 176 deletions.
2 changes: 1 addition & 1 deletion src/effect-callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
var callback = function() {
var t = callback._animation ? callback._animation.currentTime : null;
if (t !== null) {
t = shared.calculateTimeFraction(shared.calculateActiveDuration(timing), t, timing);
t = shared.calculateIterationProgress(shared.calculateActiveDuration(timing), t, timing);
if (isNaN(t))
t = null;
}
Expand Down
4 changes: 2 additions & 2 deletions src/group-constructors.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,13 @@

// If the group has a negative playback rate and is not fill backwards/both, then it should go
// out of effect when it reaches the start of its active interval (tf == 0). If it is fill
// backwards/both then it should stay in effect. calculateTimeFraction will return 0 in the
// backwards/both then it should stay in effect. calculateIterationProgress will return 0 in the
// backwards-filling case, and null otherwise.
if (tf == 0 && animation.playbackRate < 0) {
if (!timing) {
timing = shared.normalizeTimingInput(animation.effect.timing);
}
tf = shared.calculateTimeFraction(shared.calculateActiveDuration(timing), -1, timing);
tf = shared.calculateIterationProgress(shared.calculateActiveDuration(timing), -1, timing);
if (isNaN(tf) || tf == null) {
animation._forEachChild(function(child) {
child.currentTime = -1;
Expand Down
2 changes: 1 addition & 1 deletion src/keyframe-effect.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
var timeFraction = 0;
var activeDuration = shared.calculateActiveDuration(timing);
var effectTime = function(localTime) {
return shared.calculateTimeFraction(activeDuration, localTime, timing);
return shared.calculateIterationProgress(activeDuration, localTime, timing);
};
effectTime._totalDuration = timing.delay + activeDuration + timing.endDelay;
return effectTime;
Expand Down
100 changes: 65 additions & 35 deletions src/timing-utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@
}

function repeatedDuration(timing) {
// https://w3c.github.io/web-animations/#calculating-the-active-duration
if (timing.duration === 0 || timing.iterations === 0) {
return 0;
}
return timing.duration * timing.iterations;
}

Expand All @@ -273,19 +277,24 @@
var PhaseActive = 3;

function calculatePhase(activeDuration, localTime, timing) {
// https://w3c.github.io/web-animations/#animation-effect-phases-and-states
if (localTime == null) {
return PhaseNone;
}
if (localTime < timing.delay) {

var endTime = timing.delay + activeDuration + timing.endDelay;
if (localTime < Math.min(timing.delay, endTime)) {
return PhaseBefore;
}
if (localTime >= timing.delay + activeDuration) {
if (localTime >= Math.min(timing.delay + activeDuration, endTime)) {
return PhaseAfter;
}

return PhaseActive;
}

function calculateActiveTime(activeDuration, fillMode, localTime, phase, delay) {
// https://w3c.github.io/web-animations/#calculating-the-active-time
switch (phase) {
case PhaseBefore:
if (fillMode == 'backwards' || fillMode == 'both')
Expand All @@ -302,56 +311,82 @@
}
}

function calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing) {
return (timing.playbackRate < 0 ? activeTime - activeDuration : activeTime) * timing.playbackRate + startOffset;
function calculateOverallProgress(iterationDuration, phase, iterations, activeTime, iterationStart) {
// https://w3c.github.io/web-animations/#calculating-the-overall-progress
var overallProgress = iterationStart;
if (iterationDuration === 0) {
if (phase !== PhaseBefore) {
overallProgress += iterations;
}
} else {
overallProgress += activeTime / iterationDuration;
}
return overallProgress;
}

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;
}
function calculateSimpleIterationProgress(overallProgress, iterationStart, phase, iterations, activeTime, iterationDuration) {
// https://w3c.github.io/web-animations/#calculating-the-simple-iteration-progress

return scaledActiveTime % iterationDuration;
var simpleIterationProgress = (overallProgress === Infinity) ? iterationStart % 1 : overallProgress % 1;
if (simpleIterationProgress === 0 && phase === PhaseAfter && iterations !== 0 &&
(activeTime !== 0 || iterationDuration === 0)) {
simpleIterationProgress = 1;
}
return simpleIterationProgress;
}

function calculateCurrentIteration(iterationDuration, iterationTime, scaledActiveTime, timing) {
if (scaledActiveTime === 0) {
return 0;
function calculateCurrentIteration(phase, iterations, simpleIterationProgress, overallProgress) {
// https://w3c.github.io/web-animations/#calculating-the-current-iteration
if (phase === PhaseAfter && iterations === Infinity) {
return Infinity;
}
if (iterationTime == iterationDuration) {
return timing.iterationStart + timing.iterations - 1;
if (simpleIterationProgress === 1) {
return Math.floor(overallProgress) - 1;
}
return Math.floor(scaledActiveTime / iterationDuration);
return Math.floor(overallProgress);
}

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._easingFunction(timeFraction);
function calculateDirectedProgress(playbackDirection, currentIteration, simpleIterationProgress) {
// https://w3c.github.io/web-animations/#calculating-the-directed-progress
var currentDirection = playbackDirection;
if (playbackDirection !== 'normal' && playbackDirection !== 'reverse') {
var d = currentIteration;
if (playbackDirection === 'alternate-reverse') {
d += 1;
}
currentDirection = 'normal';
if (d !== Infinity && d % 2 !== 0) {
currentDirection = 'reverse';
}
}
if (currentDirection === 'normal') {
return simpleIterationProgress;
}
return 1 - simpleIterationProgress;
}

function calculateTimeFraction(activeDuration, localTime, timing) {
function calculateIterationProgress(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;

var overallProgress = calculateOverallProgress(timing.duration, phase, timing.iterations, activeTime, timing.iterationStart);
var simpleIterationProgress = calculateSimpleIterationProgress(overallProgress, timing.iterationStart, phase, timing.iterations, activeTime, timing.duration);
var currentIteration = calculateCurrentIteration(phase, timing.iterations, simpleIterationProgress, overallProgress);
var directedProgress = calculateDirectedProgress(timing.direction, currentIteration, simpleIterationProgress);

// https://w3c.github.io/web-animations/#calculating-the-transformed-progress
// https://w3c.github.io/web-animations/#calculating-the-iteration-progress
return timing._easingFunction(directedProgress);
}

shared.cloneTimingInput = cloneTimingInput;
shared.makeTiming = makeTiming;
shared.numericTimingToObject = numericTimingToObject;
shared.normalizeTimingInput = normalizeTimingInput;
shared.calculateActiveDuration = calculateActiveDuration;
shared.calculateTimeFraction = calculateTimeFraction;
shared.calculateIterationProgress = calculateIterationProgress;
shared.calculatePhase = calculatePhase;
shared.normalizeEasing = normalizeEasing;
shared.parseEasingFunction = parseEasingFunction;
Expand All @@ -366,11 +401,6 @@
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);
2 changes: 1 addition & 1 deletion src/web-animations-next-animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@
var timing = this.effect._timing;
var t = this.currentTime;
if (t !== null)
t = shared.calculateTimeFraction(shared.calculateActiveDuration(timing), t, timing);
t = shared.calculateIterationProgress(shared.calculateActiveDuration(timing), t, timing);
if (t == null || isNaN(t))
this._removeChildAnimations();
},
Expand Down
2 changes: 1 addition & 1 deletion test/js/group-animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ suite('group-animation', function() {
tick(102);
assert.equal(getComputedStyle(this.target).marginLeft, '2px');
tick(103);
assert.equal(getComputedStyle(this.target).marginLeft, '3px');
assert.equal(getComputedStyle(this.target).marginLeft, '0px');
tick(104);
});

Expand Down
65 changes: 10 additions & 55 deletions test/js/timing-utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,72 +56,27 @@ suite('timing-utilities', function() {
assert.equal(f(0.1), 0.1);
assert.equal(f(0.25), 0.2);
});
test('calculating phase', function() {
// calculatePhase(activeDuration, localTime, timing);
assert.equal(calculatePhase(1000, 100, {delay: 0}), PhaseActive);
assert.equal(calculatePhase(1000, 100, {delay: 200}), PhaseBefore);
assert.equal(calculatePhase(1000, 2000, {delay: 200}), PhaseAfter);
assert.equal(calculatePhase(1000, null, {delay: 200}), PhaseNone);
});
test('calculating active time', function() {
// calculateActiveTime(activeDuration, fillMode, localTime, phase, delay);
assert.equal(calculateActiveTime(1000, 'forwards', 100, PhaseActive, 0), 100);
assert.equal(calculateActiveTime(1000, 'forwards', 100, PhaseBefore, 200), null);
assert.equal(calculateActiveTime(1000, 'both', 100, PhaseBefore, 200), 0);
assert.equal(calculateActiveTime(1000, 'forwards', 500, PhaseActive, 200), 300);
assert.equal(calculateActiveTime(1000, 'forwards', 1100, PhaseAfter, 200), 1000);
assert.equal(calculateActiveTime(1000, 'none', 1100, PhaseAfter, 200), null);
assert.equal(calculateActiveTime(Infinity, 'both', 5000000, PhaseActive, 2000000), 3000000);
assert.equal(calculateActiveTime(Infinity, 'both', 50000, PhaseBefore, 2000000), 0);
});
test('calculating scaled active time', function() {
// calculateScaledActiveTime(activeDuration, activeTime, startOffset, timingInput);
assert.equal(calculateScaledActiveTime(1000, 200, 300, {playbackRate: 1.5}), 600);
assert.equal(calculateScaledActiveTime(1000, 200, 300, {playbackRate: -4}), 3500);
assert.equal(calculateScaledActiveTime(Infinity, 400, 200, {playbackRate: 1}), 600);
assert.equal(calculateScaledActiveTime(Infinity, 400, 200, {playbackRate: -4}), Infinity);
});
test('calculating iteration time', function() {
// calculateIterationTime(iterationDuration, repeatedDuration, scaledActiveTime, startOffset, timingInput);
assert.equal(calculateIterationTime(500, 5000, 600, 100, {iterations: 10, iterationStart: 0}), 100);
assert.equal(calculateIterationTime(500, 5000, Infinity, 100, {iterations: 10, iterationStart: 0}), 500);
assert.equal(calculateIterationTime(500, 5000, 5100, 100, {iterations: 3.2, iterationStart: 0.8}), 500);
});
test('calculating current iteration', function() {
// calculateCurrentIteration(iterationDuration, iterationTime, scaledActiveTime, timingInput);
assert.equal(calculateCurrentIteration(1000, 400, 4400, {iterations: 50, iterationStart: 0.8}), 4);
assert.equal(calculateCurrentIteration(1000, 1000, 4400, {iterations: 50.2, iterationStart: 0.8}), 50);
});
test('calculating transformed time', function() {
// calculateTransformedTime(currentIteration, iterationDuration, iterationTime, timingInput);
assert.equal(calculateTransformedTime(4, 1000, 200, {_easingFunction: function(x) { return x; }, direction: 'normal'}), 200);
assert.equal(calculateTransformedTime(4, 1000, 200, {_easingFunction: function(x) { return x; }, direction: 'reverse'}), 800);
assert.closeTo(calculateTransformedTime(4, 1000, 200, {_easingFunction: function(x) { return x * x; }, direction: 'reverse'}), 640, 0.0001);
assert.closeTo(calculateTransformedTime(4, 1000, 600, {_easingFunction: function(x) { return x * x; }, direction: 'alternate'}), 360, 0.0001);
assert.closeTo(calculateTransformedTime(3, 1000, 600, {_easingFunction: function(x) { return x * x; }, direction: 'alternate'}), 160, 0.0001);
assert.closeTo(calculateTransformedTime(4, 1000, 600, {_easingFunction: function(x) { return x * x; }, direction: 'alternate-reverse'}), 160, 0.0001);
assert.closeTo(calculateTransformedTime(3, 1000, 600, {_easingFunction: function(x) { return x * x; }, direction: 'alternate-reverse'}), 360, 0.0001);
});
test('EffectTime', function() {
var timing = normalizeTimingInput({duration: 1000, iterations: 4, iterationStart: 0.5, easing: 'linear', direction: 'alternate', delay: 100, fill: 'forwards'});
var timing2 = normalizeTimingInput({duration: 1000, iterations: 4, iterationStart: 0.5, easing: 'ease', direction: 'alternate', delay: 100, fill: 'forwards'});
var effectTF = effectTime(timing);
var effectTF2 = effectTime(timing2);
var epsilon = 0.005;
assert.equal(effectTF(0), null);
assert.equal(effectTF(100), 0.5);
assert.closeTo(effectTF2(100), 0.8, 0.005);
assert.closeTo(effectTF2(100), 0.8, epsilon);
assert.equal(effectTF(600), 1);
assert.closeTo(effectTF2(600), 1, 0.005);
assert.equal(effectTF(700), 0.9);
assert.closeTo(effectTF2(700), 0.99, 0.005);
assert.closeTo(effectTF2(600), 1, epsilon);
assert.closeTo(effectTF(700), 0.9, epsilon);
assert.closeTo(effectTF2(700), 0.99, epsilon);
assert.equal(effectTF(1600), 0);
assert.closeTo(effectTF2(1600), 0, 0.005);
assert.equal(effectTF(4000), 0.4);
assert.closeTo(effectTF2(4000), 0.68, 0.005);
assert.closeTo(effectTF2(1600), 0, epsilon);
assert.closeTo(effectTF(4000), 0.4, epsilon);
assert.closeTo(effectTF2(4000), 0.68, epsilon);
assert.equal(effectTF(4100), 0.5);
assert.closeTo(effectTF2(4100), 0.8, 0.005);
assert.closeTo(effectTF2(4100), 0.8, epsilon);
assert.equal(effectTF(6000), 0.5);
assert.closeTo(effectTF2(6000), 0.8, 0.005);
assert.closeTo(effectTF2(6000), 0.8, epsilon);
});
test('TypeErrors', function() {
var timing = normalizeTimingInput({
Expand Down
Loading

0 comments on commit b85f9a3

Please sign in to comment.