blob: c827d10c50bcc3f0937fe6a5cda752fba4142197 [file] [log] [blame]
<!DOCTYPE html>
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<style>
main > div {
overflow: hidden;
width: 100px;
height: 100px;
}
main > div > div {
height: 200px;
}
@keyframes expand {
from { width: 100px; }
to { width: 200px; }
}
#element {
width: 0px;
height: 20px;
animation-name: expand;
/* Some of the tests in this file assume animations attached to the
DocumentTimeline are "stopped" without actually being paused.
Using 600s + steps(10, end) achieves this for one minute.*/
animation-duration: 600s;
animation-timing-function: steps(10, end);
}
</style>
<main>
<div id=scroller1><div></div></div>
<div id=scroller2><div></div></div>
<div id=container></div>
</main>
<script>
// Force layout of scrollers.
scroller1.offsetTop;
scroller2.offsetTop;
// Note the steps(10, end) timing function and height:100px. (10px scroll
// resolution).
scroller1.scrollTop = 20;
scroller2.scrollTop = 40;
function insertElement() {
let element = document.createElement('div');
element.id = 'element';
container.append(element);
return element;
}
// Runs a test with dynamically added/removed elements or CSS rules.
// Each test is instantiated twice: once for the initial style resolve where
// the DOM change was effectuated, and once after scrolling.
function dynamic_rule_test(func, description) {
// assert_width is an async function which verifies that the computed value
// of 'width' is as expected.
const instantiate = (assert_width, description) => {
promise_test(async (t) => {
try {
await func(t, assert_width);
} finally {
while (container.firstChild)
container.firstChild.remove();
scroller1.style = '';
scroller2.style = '';
}
}, description);
};
// Verify that the computed style is as expected immediately after the
// rule change took place.
instantiate(async (element, expected) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element).width, expected);
}, description + ' [immediate]');
// Verify that the computed style after scrolling a bit.
instantiate(async (element, expected) => {
scroller1.scrollTop = scroller1.scrollTop + 10;
scroller2.scrollTop = scroller2.scrollTop + 10;
await waitForNextFrame();
scroller1.scrollTop = scroller1.scrollTop - 10;
scroller2.scrollTop = scroller2.scrollTop - 10;
await waitForNextFrame();
assert_equals(getComputedStyle(element).width, expected);
}, description + ' [scroll]');
}
dynamic_rule_test(async (t, assert_width) => {
let element = insertElement();
// This element initially has a DocumentTimeline.
await assert_width(element, '100px');
// Switch to scroll timeline.
scroller1.style.scrollTimelineName = 'timeline';
element.style.animationTimeline = 'timeline';
await assert_width(element, '120px');
// Switching from ScrollTimeline -> DocumentTimeline should preserve
// current time.
scroller1.style = '';
element.style = '';
await assert_width(element, '120px');
}, 'Switching between document and scroll timelines');
dynamic_rule_test(async (t, assert_width) => {
let element = insertElement();
// Note: #scroller1 is at 20%, and #scroller2 is at 40%.
scroller1.style.scrollTimelineName = 'timeline1';
scroller2.style.scrollTimelineName = 'timeline2';
await assert_width(element, '100px');
element.style.animationTimeline = 'timeline1';
await assert_width(element, '120px');
element.style.animationTimeline = 'timeline2';
await assert_width(element, '140px');
element.style.animationTimeline = 'timeline1';
await assert_width(element, '120px');
// Switching from ScrollTimeline -> DocumentTimeline should preserve
// current time.
element.style.animationTimeline = '';
await assert_width(element, '120px');
}, 'Changing computed value of animation-timeline changes effective timeline');
dynamic_rule_test(async (t, assert_width) => {
let element = insertElement();
scroller1.style.scrollTimelineName = 'timeline';
// DocumentTimeline applies by default.
await assert_width(element, '100px');
// DocumentTimeline -> none
element.style.animationTimeline = 'none';
await assert_width(element, '0px');
// none -> DocumentTimeline
element.style.animationTimeline = '';
await assert_width(element, '100px');
// DocumentTimeline -> ScrollTimeline
element.style.animationTimeline = 'timeline';
await assert_width(element, '120px');
// ScrollTimeline -> none
element.style.animationTimeline = 'none';
await assert_width(element, '0px');
// none -> ScrollTimeline
element.style.animationTimeline = 'timeline';
await assert_width(element, '120px');
}, 'Changing to/from animation-timeline:none');
dynamic_rule_test(async (t, assert_width) => {
let element = insertElement();
element.style.animationTimeline = 'timeline';
// Unknown animation-timeline, current time held at zero.
await assert_width(element, '100px');
scroller1.style.scrollTimelineName = 'timeline';
await assert_width(element, '120px');
scroller2.style.scrollTimelineName = 'timeline';
await assert_width(element, '140px');
}, 'Changing scroll-timeline on preceding elements affects target element');
dynamic_rule_test(async (t, assert_width) => {
let element = insertElement();
element.style.animationDirection = 'reverse';
element.style.animationTimeline = 'timeline';
// Unknown animation-timeline, current time held at zero.
await assert_width(element, '200px');
// Note: #scroller1 is at 20%.
scroller1.style.scrollTimelineName = 'timeline';
await assert_width(element, '180px');
// Note: #scroller2 is at 40%.
scroller2.style.scrollTimelineName = 'timeline';
await assert_width(element, '160px');
element.style.animationDirection = '';
await assert_width(element, '140px');
}, 'Reverse animation direction');
dynamic_rule_test(async (t, assert_width) => {
let element = insertElement();
element.style.animationTimeline = 'timeline';
// Unknown animation-timeline, current time held at zero.
await assert_width(element, '100px');
// Note: #scroller1 is at 20%.
scroller1.style.scrollTimelineName = 'timeline';
await assert_width(element, '120px');
element.style.animationPlayState = 'paused';
// We should still be at the same position after pausing.
await assert_width(element, '120px');
// Note: #scroller2 is at 40%.
scroller2.style.scrollTimelineName = 'timeline';
// Even when switching timelines, we should be at the same position until
// we unpause.
await assert_width(element, '120px');
// Unpausing should synchronize to the scroll position.
element.style.animationPlayState = '';
await assert_width(element, '140px');
}, 'Switching timelines while paused');
dynamic_rule_test(async (t, assert_width) => {
let element = insertElement();
// Note: #scroller1 is at 20%.
scroller1.style.scrollTimelineName = 'timeline';
await assert_width(element, '100px');
element.style.animationTimeline = 'timeline';
element.style.animationPlayState = 'paused';
// Pausing should happen before the timeline is modified. (Tentative).
// https://github.com/w3c/csswg-drafts/issues/5653
await assert_width(element, '100px');
element.style.animationPlayState = 'running';
await assert_width(element, '120px');
}, 'Switching timelines and pausing at the same time');
</script>