322 lines
9.4 KiB
HTML
322 lines
9.4 KiB
HTML
<!DOCTYPE html>
|
|
<title>Behavior of the timeline-scope property</title>
|
|
<link rel="help" src="https://github.com/w3c/csswg-drafts/issues/7759">
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/web-animations/testcommon.js"></script>
|
|
|
|
<main id=main></main>
|
|
<script>
|
|
async function inflate(t, template) {
|
|
t.add_cleanup(() => main.replaceChildren());
|
|
return runAndWaitForFrameUpdate(() => {
|
|
main.append(template.content.cloneNode(true));
|
|
});
|
|
}
|
|
|
|
async function scrollTop(e, value) {
|
|
e.scrollTop = value;
|
|
await waitForNextFrame();
|
|
}
|
|
</script>
|
|
<style>
|
|
@keyframes anim {
|
|
from { width: 0px; }
|
|
to { width: 200px; }
|
|
}
|
|
|
|
.scroller {
|
|
overflow-y: hidden;
|
|
width: 200px;
|
|
height: 200px;
|
|
}
|
|
.scroller > .content {
|
|
margin: 400px 0px;
|
|
width: 100px;
|
|
height: 100px;
|
|
background-color: green;
|
|
}
|
|
.target {
|
|
background-color: coral;
|
|
width: 0px;
|
|
animation: anim auto linear;
|
|
animation-timeline: --t1;
|
|
}
|
|
.timeline {
|
|
scroll-timeline-name: --t1;
|
|
}
|
|
.scope {
|
|
timeline-scope: --t1;
|
|
}
|
|
|
|
</style>
|
|
|
|
<!-- Basic Behavior -->
|
|
|
|
<template id=deferred_timeline>
|
|
<div class="scope">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
|
|
const anim = target.getAnimations()[0];
|
|
await anim.ready;
|
|
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Descendant can attach to deferred timeline');
|
|
</script>
|
|
|
|
<template id=deferred_timeline_no_attachments>
|
|
<div class="scope">
|
|
<div class=target>Test</div>
|
|
<div class="scroller">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_no_attachments);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
}, 'Deferred timeline with no attachments');
|
|
</script>
|
|
|
|
<template id=scroll_timeline_inner_interference>
|
|
<div class="scroller timeline">
|
|
<div class=content>
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, scroll_timeline_inner_interference);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Inner timeline does not interfere with outer timeline');
|
|
</script>
|
|
|
|
<template id=deferred_timeline_two_attachments>
|
|
<div class="scope">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
<!-- Extra attachment -->
|
|
<div class="timeline"></div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_two_attachments);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
}, 'Deferred timeline with two attachments');
|
|
</script>
|
|
|
|
<!-- Dynamic Reattachment -->
|
|
|
|
<template id=deferred_timeline_reattach>
|
|
<div class="scope">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
<div class="scroller">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_reattach);
|
|
let scrollers = main.querySelectorAll('.scroller');
|
|
assert_equals(scrollers.length, 2);
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scrollers[0], 350); // 50%
|
|
await scrollTop(scrollers[1], 175); // 25%
|
|
|
|
// Attached to scrollers[0].
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
// Reattach to scrollers[1].
|
|
await runAndWaitForFrameUpdate(() => {
|
|
scrollers[0].classList.remove('timeline');
|
|
scrollers[1].classList.add('timeline');
|
|
});
|
|
|
|
assert_equals(getComputedStyle(target).width, '50px'); // 0px => 200px, 25%
|
|
}, 'Dynamically re-attaching');
|
|
</script>
|
|
|
|
<template id=deferred_timeline_dynamic_detach>
|
|
<div class="scope">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_dynamic_detach);
|
|
let scrollers = main.querySelectorAll('.scroller');
|
|
assert_equals(scrollers.length, 2);
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scrollers[0], 350); // 50%
|
|
await scrollTop(scrollers[1], 175); // 25%
|
|
|
|
// Attached to two timelines initially:
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
|
|
// Detach scrollers[1].
|
|
await runAndWaitForFrameUpdate(() => {
|
|
scrollers[1].classList.remove('timeline');
|
|
});
|
|
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
// Also detach scrollers[0].
|
|
scrollers[0].classList.remove('timeline');
|
|
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
}, 'Dynamically detaching');
|
|
</script>
|
|
|
|
<template id=deferred_timeline_attached_removed>
|
|
<div class="scope">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_attached_removed);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
let scroller_parent = scroller.parentElement;
|
|
scroller.remove();
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
|
|
scroller_parent.append(scroller);
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Removing/inserting element with attaching timeline');
|
|
</script>
|
|
|
|
<template id=deferred_timeline_attached_display_none>
|
|
<div class="scope">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_attached_display_none);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
scroller.style.display = 'none';
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
|
|
scroller.style.display = 'block';
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Ancestor attached element becoming display:none/block');
|
|
</script>
|
|
|
|
<template id=deferred_timeline_appearing>
|
|
<div class=container>
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_appearing);
|
|
let container = main.querySelector('.container');
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
|
|
await scrollTop(scroller, 350); // 50%
|
|
|
|
// Not attached to any timeline initially.
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
|
|
// Add the deferred timeline.
|
|
container.classList.add('scope');
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
// Remove the deferred timeline.
|
|
container.classList.remove('scope');
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
}, 'A deferred timeline appearing dynamically in the ancestor chain');
|
|
</script>
|
|
|
|
<template id=deferred_timeline_on_self>
|
|
<div class="scroller timeline scope">
|
|
<div class=content>
|
|
<div class=target></div>
|
|
</div>
|
|
<div class=scroller2></div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
await inflate(t, deferred_timeline_on_self);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 525); // 75%
|
|
|
|
assert_equals(getComputedStyle(target).width, '150px'); // 0px => 200px, 75%
|
|
|
|
// A second scroll-timeline now attaches to the same root.
|
|
let scroller2 = main.querySelector('.scroller2');
|
|
scroller2.classList.add('timeline');
|
|
await waitForNextFrame();
|
|
|
|
// The deferred timeline produced by timeline-scope is now inactive,
|
|
// but it doesn't matter, because we preferred to attach
|
|
// to the non-deferred timeline.
|
|
assert_equals(getComputedStyle(target).width, '150px'); // 0px => 200px, 75%
|
|
}, 'Animations prefer non-deferred timelines');
|
|
|
|
</script>
|