mirror of
https://github.com/servo/servo.git
synced 2025-10-09 13:09:25 +01:00
394 lines
13 KiB
HTML
394 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<meta charset=utf-8>
|
|
<title>Setting the timeline of scroll animation</title>
|
|
<link rel="help"
|
|
href="https://drafts.csswg.org/web-animations-1/#setting-the-timeline">
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/web-animations/testcommon.js"></script>
|
|
<script src="testcommon.js"></script>
|
|
<style>
|
|
.scroller {
|
|
overflow: auto;
|
|
height: 200px;
|
|
width: 100px;
|
|
will-change: transform;
|
|
}
|
|
.contents {
|
|
/* The height is set to align scrolling in pixels with logical time in ms */
|
|
height: 1200px;
|
|
width: 100%;
|
|
}
|
|
@keyframes anim {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
.anim {
|
|
animation: anim 1s paused linear;
|
|
}
|
|
</style>
|
|
<body>
|
|
<script>
|
|
'use strict';
|
|
|
|
function createAnimation(t) {
|
|
const elem = createDiv(t);
|
|
const animation = elem.animate({ opacity: [1, 0] }, 1000);
|
|
return animation;
|
|
}
|
|
|
|
function createPausedCssAnimation(t) {
|
|
const elem = createDiv(t);
|
|
elem.classList.add('anim');
|
|
return elem.getAnimations()[0];
|
|
}
|
|
|
|
function updateScrollPosition(timeline, offset) {
|
|
const scroller = timeline.source;
|
|
assert_true(!!scroller, 'source is resolved');
|
|
scroller.scrollTop = offset;
|
|
// Wait for new animation frame which allows the timeline to compute new
|
|
// current time.
|
|
return waitForNextFrame();
|
|
}
|
|
|
|
function assert_timeline_current_time(animation, timeline_current_time) {
|
|
if (animation.currentTime instanceof CSSUnitValue){
|
|
assert_percents_equal(animation.timeline.currentTime, timeline_current_time,
|
|
`Timeline's currentTime aligns with the scroll ` +
|
|
`position even when paused`);
|
|
}
|
|
else {
|
|
assert_times_equal(animation.timeline.currentTime, timeline_current_time,
|
|
`Timeline's currentTime aligns with the scroll ` +
|
|
`position even when paused`);
|
|
}
|
|
}
|
|
|
|
function assert_scroll_synced_times(animation, timeline_current_time,
|
|
animation_current_time) {
|
|
assert_timeline_current_time(animation, timeline_current_time);
|
|
if (animation.currentTime instanceof CSSUnitValue){
|
|
assert_percents_equal(animation.currentTime, animation_current_time,
|
|
`Animation's currentTime aligns with the scroll position`);
|
|
}
|
|
else {
|
|
assert_times_equal(animation.currentTime, animation_current_time,
|
|
`Animation's currentTime aligns with the scroll position`);
|
|
}
|
|
}
|
|
|
|
function assert_paused_times(animation, timeline_current_time,
|
|
animation_current_time) {
|
|
assert_timeline_current_time(animation, timeline_current_time);
|
|
if (animation.currentTime instanceof CSSUnitValue){
|
|
assert_percents_equal(animation.currentTime, animation_current_time,
|
|
`Animation's currentTime is fixed while paused`);
|
|
}
|
|
else {
|
|
assert_times_equal(animation.currentTime, animation_current_time,
|
|
`Animation's currentTime is fixed while paused`);
|
|
}
|
|
}
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.timeline = scrollTimeline;
|
|
assert_true(animation.pending);
|
|
await animation.ready;
|
|
|
|
assert_equals(animation.playState, 'running');
|
|
assert_scroll_synced_times(animation, 10, 10);
|
|
}, 'Setting a scroll timeline on a play-pending animation synchronizes ' +
|
|
'currentTime of the animation with the scroll position.');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.pause();
|
|
animation.timeline = scrollTimeline;
|
|
assert_true(animation.pending);
|
|
await animation.ready;
|
|
|
|
assert_equals(animation.playState, 'paused');
|
|
assert_paused_times(animation, 10, 0);
|
|
|
|
await updateScrollPosition(animation.timeline, 200);
|
|
|
|
assert_equals(animation.playState, 'paused');
|
|
assert_paused_times(animation, 20, 0);
|
|
|
|
animation.play();
|
|
await animation.ready;
|
|
|
|
assert_scroll_synced_times(animation, 20, 20);
|
|
}, 'Setting a scroll timeline on a pause-pending animation fixes the ' +
|
|
'currentTime of the animation based on the scroll position once resumed');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.reverse();
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
|
|
assert_equals(animation.playState, 'running');
|
|
assert_scroll_synced_times(animation, 10, 90);
|
|
}, 'Setting a scroll timeline on a reversed play-pending animation ' +
|
|
'synchronizes the currentTime of the animation with the scroll ' +
|
|
'position.');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
await animation.ready;
|
|
|
|
animation.timeline = scrollTimeline;
|
|
assert_false(animation.pending);
|
|
assert_equals(animation.playState, 'running');
|
|
assert_scroll_synced_times(animation, 10, 10);
|
|
}, 'Setting a scroll timeline on a running animation synchronizes the ' +
|
|
'currentTime of the animation with the scroll position.');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.pause();
|
|
await animation.ready;
|
|
|
|
animation.timeline = scrollTimeline;
|
|
assert_false(animation.pending);
|
|
assert_equals(animation.playState, 'paused');
|
|
assert_paused_times(animation, 10, 0);
|
|
|
|
animation.play();
|
|
await animation.ready;
|
|
|
|
assert_scroll_synced_times(animation, 10, 10);
|
|
}, 'Setting a scroll timeline on a paused animation fixes the currentTime of ' +
|
|
'the animation based on the scroll position when resumed');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.reverse();
|
|
animation.pause();
|
|
await animation.ready;
|
|
|
|
animation.timeline = scrollTimeline;
|
|
assert_false(animation.pending);
|
|
assert_equals(animation.playState, 'paused');
|
|
assert_paused_times(animation, 10, 100);
|
|
|
|
animation.play();
|
|
await animation.ready;
|
|
|
|
assert_scroll_synced_times(animation, 10, 90);
|
|
}, 'Setting a scroll timeline on a reversed paused animation ' +
|
|
'fixes the currentTime of the animation based on the scroll ' +
|
|
'position when resumed');
|
|
|
|
promise_test(async t => {
|
|
const animation = createAnimation(t);
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
animation.timeline = document.timeline;
|
|
assert_times_equal(animation.currentTime, 100);
|
|
}, 'Transitioning from a scroll timeline to a document timeline on a running ' +
|
|
'animation preserves currentTime');
|
|
|
|
promise_test(async t => {
|
|
const animation = createAnimation(t);
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
animation.pause();
|
|
animation.timeline = document.timeline;
|
|
|
|
await animation.ready;
|
|
assert_times_equal(animation.currentTime, 100);
|
|
}, 'Transitioning from a scroll timeline to a document timeline on a ' +
|
|
'pause-pending animation preserves currentTime');
|
|
|
|
promise_test(async t => {
|
|
const animation = createAnimation(t);
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
|
|
animation.reverse();
|
|
animation.pause();
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
assert_scroll_synced_times(animation, 10, 90);
|
|
|
|
await animation.ready;
|
|
|
|
animation.timeline = document.timeline;
|
|
assert_false(animation.pending);
|
|
assert_equals(animation.playState, 'paused');
|
|
assert_times_equal(animation.currentTime, 900);
|
|
}, 'Transition from a scroll timeline to a document timeline on a reversed ' +
|
|
'paused animation maintains correct currentTime');
|
|
|
|
promise_test(async t => {
|
|
const animation = createAnimation(t);
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const progress = animation.currentTime.value / 100;
|
|
const duration = animation.effect.getTiming().duration;
|
|
animation.timeline = null;
|
|
|
|
const expectedCurrentTime = progress * duration;
|
|
assert_times_equal(animation.currentTime, expectedCurrentTime);
|
|
}, 'Transitioning from a scroll timeline to a null timeline on a running ' +
|
|
'animation preserves current progress.');
|
|
|
|
promise_test(async t => {
|
|
const keyframeEfect = new KeyframeEffect(createDiv(t),
|
|
{ opacity: [0, 1] },
|
|
1000);
|
|
const animation = new Animation(keyframeEfect, null);
|
|
animation.startTime = 0;
|
|
assert_equals(animation.playState, 'running');
|
|
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
animation.timeline = scrollTimeline;
|
|
assert_equals(animation.playState, 'running');
|
|
assert_percents_equal(animation.currentTime, 10);
|
|
}, 'Switching from a null timeline to a scroll timeline on an animation with ' +
|
|
'a resolved start time preserved the play state');
|
|
|
|
promise_test(async t => {
|
|
const firstScrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(firstScrollTimeline, 100);
|
|
|
|
const secondScrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(secondScrollTimeline, 200);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.timeline = firstScrollTimeline;
|
|
await animation.ready;
|
|
assert_percents_equal(animation.currentTime, 10);
|
|
|
|
animation.timeline = secondScrollTimeline;
|
|
assert_percents_equal(animation.currentTime, 20);
|
|
}, 'Switching from one scroll timeline to another updates currentTime');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createPausedCssAnimation(t);
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
assert_equals(animation.playState, 'paused');
|
|
assert_percents_equal(animation.currentTime, 0);
|
|
|
|
const target = animation.effect.target;
|
|
target.style.animationPlayState = 'running';
|
|
await animation.ready;
|
|
|
|
assert_percents_equal(animation.currentTime, 10);
|
|
}, 'Switching from a document timeline to a scroll timeline updates ' +
|
|
'currentTime when unpaused via CSS.');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.pause();
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
assert_percents_equal(animation.currentTime, 0);
|
|
|
|
animation.currentTime = CSSNumericValue.parse("50%");
|
|
|
|
animation.play();
|
|
await animation.ready;
|
|
assert_percents_equal(animation.currentTime, 50);
|
|
}, 'Switching from a document timeline to a scroll timeline and updating ' +
|
|
'currentTime preserves the new value when unpaused.');
|
|
|
|
promise_test(async t => {
|
|
const scrollTimeline = createScrollTimeline(t);
|
|
await updateScrollPosition(scrollTimeline, 100);
|
|
|
|
const animation = createAnimation(t);
|
|
animation.pause();
|
|
animation.timeline = scrollTimeline;
|
|
await animation.ready;
|
|
assert_percents_equal(animation.currentTime, 0);
|
|
assert_equals(animation.playState, 'paused');
|
|
|
|
// Set a start time that will ensure that current time is in the active
|
|
// region in order not to trigger a seek when play is called.
|
|
animation.startTime = CSSNumericValue.parse("-10%");
|
|
assert_equals(animation.playState, 'running');
|
|
|
|
animation.play();
|
|
await animation.ready;
|
|
assert_percents_equal(animation.startTime, -10);
|
|
}, 'Switching from a document timeline to a scroll timeline and updating ' +
|
|
'startTime preserves the new value when play is called.');
|
|
|
|
promise_test(async t => {
|
|
const elem = createDiv(t);
|
|
const animation = elem.animate(null, Infinity);
|
|
await animation.ready;
|
|
|
|
animation.timeline = new ScrollTimeline();
|
|
let timing = animation.effect.getComputedTiming();
|
|
assert_percents_equal(timing.endTime, 100);
|
|
assert_percents_equal(timing.activeDuration, 100);
|
|
assert_percents_equal(timing.duration, 100);
|
|
|
|
animation.effect.updateTiming({ iterations: 2 });
|
|
timing = animation.effect.getComputedTiming();
|
|
assert_percents_equal(timing.endTime, 100);
|
|
assert_percents_equal(timing.activeDuration, 100);
|
|
assert_percents_equal(timing.duration, 50);
|
|
|
|
// Blink implementation does not permit setting an infinite number of
|
|
// iterations on a scroll-linked animation. Workaround by temporarily
|
|
// switching back to a document timeline.
|
|
animation.timeline = document.timeline;
|
|
animation.effect.updateTiming({ iterations: Infinity });
|
|
animation.timeline = new ScrollTimeline();
|
|
timing = animation.effect.getComputedTiming();
|
|
// Having an infinite number of iterations with a finite timeline results in
|
|
// each iteration having zero duration.
|
|
assert_percents_equal(timing.duration, 0);
|
|
// If either the iteration duration or iteration count is zero, the active
|
|
// duration is always zero.
|
|
assert_percents_equal(timing.activeDuration, 0);
|
|
assert_percents_equal(timing.endTime, 0);
|
|
|
|
}, 'Switching from a document timeline to a scroll timeline on an infinite ' +
|
|
'duration animation.')
|
|
|
|
</script>
|
|
</body>
|