Skip to content

Commit 20e1b27

Browse files
Make scroll timelines support single-axis scroll containers
Scroll timelines set to `nearest` and view timelines now resolve to the nearest ancestor scroll container for the requested axis. For these cases, the writing mode of the nearest ancestor scroll container is used. Add the writing mode used to resolve logical axes to the timeline snapshot, and hook this up to the compositor timeline. Fixed: 473576118 Bug: 440038212 Change-Id: Ic7e920cbca2146f74259e965d137ffa60ffe7f42 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7880064 Auto-Submit: Free Debreuil <freedebreuil@google.com> Reviewed-by: Robert Flack <flackr@chromium.org> Commit-Queue: Free Debreuil <freedebreuil@google.com> Cr-Commit-Position: refs/heads/main@{#1657374}
1 parent d32b11a commit 20e1b27

7 files changed

Lines changed: 343 additions & 0 deletions
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>ScrollTimeline source lookup with single-axis scroll containers (inactive)</title>
4+
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines-anonymous">
5+
<link rel="author" title="Free Debreuil" href="mailto:freedebreuil@google.com">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<script src="/web-animations/testcommon.js"></script>
9+
<script src="./testcommon.js"></script>
10+
<style>
11+
#scroller {
12+
width: 100px;
13+
height: 100px;
14+
overflow-x: clip;
15+
overflow-y: scroll;
16+
}
17+
#subject {
18+
width: 50px;
19+
height: 200px;
20+
animation: noop 1s;
21+
animation-timeline: scroll(nearest inline);
22+
}
23+
@keyframes noop {}
24+
</style>
25+
26+
<div id="scroller">
27+
<div id="subject"></div>
28+
</div>
29+
30+
<script>
31+
test(() => {
32+
// Force layout so the timeline's construction-time snapshot sees it.
33+
scroller.offsetHeight;
34+
const jsTimeline = new ScrollTimeline({ source: scroller, axis: 'x' });
35+
assert_equals(jsTimeline.source, scroller);
36+
assert_equals(jsTimeline.currentTime, null);
37+
}, 'JS-created ScrollTimeline for a non-scrollable axis is inactive');
38+
39+
test(() => {
40+
scroller.offsetHeight;
41+
const animations = subject.getAnimations();
42+
assert_equals(animations.length, 1);
43+
const cssTimeline = animations[0].timeline;
44+
assert_equals(cssTimeline.source, document.documentElement);
45+
assert_equals(cssTimeline.currentTime, null);
46+
}, 'CSS anonymous ScrollTimeline falls back when no ancestor matches the axis');
47+
48+
promise_test(async t => {
49+
t.add_cleanup(() => {
50+
scroller.style.display = '';
51+
});
52+
scroller.offsetHeight;
53+
const jsTimeline = new ScrollTimeline({ source: scroller, axis: 'y' });
54+
assert_not_equals(jsTimeline.currentTime, null);
55+
56+
scroller.style.display = 'none';
57+
await waitForNextFrame();
58+
await waitForNextFrame();
59+
60+
assert_equals(jsTimeline.currentTime, null);
61+
}, 'ScrollTimeline becomes inactive when the reference element has display:none');
62+
</script>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>ScrollTimeline source lookup with single-axis scroll containers (inner)</title>
4+
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines-anonymous">
5+
<link rel="author" title="Free Debreuil" href="mailto:freedebreuil@google.com">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<style>
9+
.block-scroller,
10+
.inline-scroller,
11+
#subject {
12+
width: 100px;
13+
height: 100px;
14+
}
15+
.block-scroller {
16+
overflow-block: scroll;
17+
overflow-inline: clip;
18+
}
19+
.inline-scroller {
20+
overflow-block: clip;
21+
overflow-inline: scroll;
22+
}
23+
.vertical-writing-mode { writing-mode: vertical-rl; }
24+
.horizontal-writing-mode { writing-mode: horizontal-tb; }
25+
#subject {
26+
animation: noop 1s;
27+
animation-timeline: scroll(nearest inline);
28+
}
29+
@keyframes noop {}
30+
</style>
31+
32+
<div id="outer" class="horizontal-writing-mode block-scroller">
33+
<div id="inner" class="vertical-writing-mode inline-scroller">
34+
<div id="subject" class="horizontal-writing-mode"></div>
35+
</div>
36+
</div>
37+
38+
<script>
39+
test(() => {
40+
const animations = subject.getAnimations();
41+
assert_equals(animations.length, 1);
42+
assert_equals(animations[0].timeline.source, inner);
43+
}, 'ScrollTimeline selects the nearest matching ancestor scroll container');
44+
</script>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>ScrollTimeline source lookup with single-axis scroll containers (outer)</title>
4+
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines-anonymous">
5+
<link rel="author" title="Free Debreuil" href="mailto:freedebreuil@google.com">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<style>
9+
.inline-scroller,
10+
#subject {
11+
width: 100px;
12+
height: 100px;
13+
}
14+
.inline-scroller {
15+
overflow-block: clip;
16+
overflow-inline: scroll;
17+
}
18+
.vertical-writing-mode { writing-mode: vertical-rl; }
19+
.horizontal-writing-mode { writing-mode: horizontal-tb; }
20+
#subject {
21+
animation: noop 1s;
22+
animation-timeline: scroll(nearest block);
23+
}
24+
@keyframes noop {}
25+
</style>
26+
27+
<div id="outer" class="horizontal-writing-mode inline-scroller">
28+
<div id="inner" class="vertical-writing-mode inline-scroller">
29+
<div id="subject" class="horizontal-writing-mode"></div>
30+
</div>
31+
</div>
32+
33+
<script>
34+
test(() => {
35+
const animations = subject.getAnimations();
36+
assert_equals(animations.length, 1);
37+
assert_equals(animations[0].timeline.source, outer);
38+
}, 'ScrollTimeline skips non-matching ancestor scroll containers');
39+
</script>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>ScrollTimeline progress with single-axis scroll containers</title>
4+
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines-anonymous">
5+
<link rel="author" title="Free Debreuil" href="mailto:freedebreuil@google.com">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<script src="/web-animations/testcommon.js"></script>
9+
<script src="./testcommon.js"></script>
10+
<style>
11+
.inline-scroller {
12+
overflow-block: clip;
13+
overflow-inline: scroll;
14+
}
15+
.vertical-writing-mode { writing-mode: vertical-rl; }
16+
.horizontal-writing-mode { writing-mode: horizontal-tb; }
17+
#outer {
18+
width: 100px;
19+
height: 100px;
20+
}
21+
#inner {
22+
width: 300px;
23+
height: 100px;
24+
}
25+
#subject {
26+
width: 50px;
27+
height: 50px;
28+
animation: noop 1s;
29+
animation-timeline: scroll(nearest block);
30+
}
31+
@keyframes noop {}
32+
</style>
33+
34+
<div id="outer" class="horizontal-writing-mode inline-scroller">
35+
<div id="inner" class="vertical-writing-mode inline-scroller">
36+
<div id="subject" class="horizontal-writing-mode"></div>
37+
</div>
38+
</div>
39+
40+
<script>
41+
promise_test(async t => {
42+
t.add_cleanup(() => { outer.scrollLeft = 0; });
43+
const timeline = subject.getAnimations()[0].timeline;
44+
assert_equals(timeline.source, outer);
45+
46+
outer.scrollLeft = (outer.scrollWidth - outer.clientWidth) / 2;
47+
await waitForNextFrame();
48+
49+
assert_percents_equal(timeline.currentTime, 50);
50+
}, 'Timeline progress follows the matched source\'s scroll offset');
51+
52+
promise_test(async t => {
53+
t.add_cleanup(() => {
54+
outer.style.direction = '';
55+
outer.scrollLeft = 0;
56+
});
57+
outer.style.direction = 'rtl';
58+
const timeline = subject.getAnimations()[0].timeline;
59+
assert_equals(timeline.source, outer);
60+
61+
outer.scrollLeft = -(outer.scrollWidth - outer.clientWidth) / 2;
62+
await waitForNextFrame();
63+
64+
assert_percents_equal(timeline.currentTime, 50);
65+
}, 'Timeline progress is measured from the matched source\'s scroll origin');
66+
</script>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>ScrollTimeline source lookup with single-axis scroll containers (viewport writing mode)</title>
4+
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines-anonymous">
5+
<link rel="author" title="Free Debreuil" href="mailto:freedebreuil@google.com">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<script src="/web-animations/testcommon.js"></script>
9+
<script src="./testcommon.js"></script>
10+
<style>
11+
html {
12+
writing-mode: horizontal-tb;
13+
overflow-x: clip;
14+
overflow-y: scroll;
15+
}
16+
body {
17+
margin: 0;
18+
writing-mode: vertical-rl;
19+
}
20+
#subject {
21+
width: 10px;
22+
height: 10px;
23+
animation: noop 1s;
24+
animation-timeline: scroll(nearest block);
25+
}
26+
#overflow {
27+
width: 10px;
28+
height: 200vh;
29+
}
30+
@keyframes noop {}
31+
</style>
32+
33+
<div id="subject"></div>
34+
<div id="overflow"></div>
35+
36+
<script>
37+
promise_test(async t => {
38+
t.add_cleanup(() => {
39+
document.body.style.contain = '';
40+
});
41+
42+
const animations = subject.getAnimations();
43+
assert_equals(animations.length, 1);
44+
const timeline = animations[0].timeline;
45+
46+
await waitForNextFrame();
47+
48+
assert_equals(timeline.source, document.documentElement);
49+
assert_equals(
50+
timeline.currentTime, null,
51+
'Timeline is inactive when block resolves to the horizontal viewport axis (clipped)');
52+
53+
document.body.style.contain = 'style';
54+
await waitForNextFrame();
55+
56+
assert_equals(timeline.source, document.documentElement);
57+
assert_percents_equal(
58+
timeline.currentTime, 0,
59+
'Timeline is active when body writing-mode propagation is blocked and block resolves to vertical viewport axis');
60+
}, 'ScrollTimeline resolves the viewport fallback axis using the viewport writing mode');
61+
</script>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>ViewTimeline source lookup with single-axis scroll containers (inner)</title>
4+
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#viewtimeline-interface">
5+
<link rel="author" title="Free Debreuil" href="mailto:freedebreuil@google.com">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<style>
9+
.block-scroller,
10+
.inline-scroller,
11+
#subject {
12+
width: 100px;
13+
height: 100px;
14+
}
15+
.block-scroller {
16+
overflow-block: scroll;
17+
overflow-inline: clip;
18+
}
19+
.inline-scroller {
20+
overflow-block: clip;
21+
overflow-inline: scroll;
22+
}
23+
.vertical-writing-mode { writing-mode: vertical-rl; }
24+
.horizontal-writing-mode { writing-mode: horizontal-tb; }
25+
</style>
26+
27+
<div id="outer" class="horizontal-writing-mode block-scroller">
28+
<div id="inner" class="vertical-writing-mode inline-scroller">
29+
<div id="subject" class="horizontal-writing-mode"></div>
30+
</div>
31+
</div>
32+
33+
<script>
34+
test(() => {
35+
const timeline = new ViewTimeline({ subject, axis: 'inline' });
36+
assert_equals(timeline.source, inner);
37+
}, 'ViewTimeline selects the nearest matching ancestor scroll container');
38+
</script>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>ViewTimeline source lookup with single-axis scroll containers (outer)</title>
4+
<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#viewtimeline-interface">
5+
<link rel="author" title="Free Debreuil" href="mailto:freedebreuil@google.com">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<style>
9+
.inline-scroller,
10+
#subject {
11+
width: 100px;
12+
height: 100px;
13+
}
14+
.inline-scroller {
15+
overflow-block: clip;
16+
overflow-inline: scroll;
17+
}
18+
.vertical-writing-mode { writing-mode: vertical-rl; }
19+
.horizontal-writing-mode { writing-mode: horizontal-tb; }
20+
</style>
21+
22+
<div id="outer" class="horizontal-writing-mode inline-scroller">
23+
<div id="inner" class="vertical-writing-mode inline-scroller">
24+
<div id="subject" class="horizontal-writing-mode"></div>
25+
</div>
26+
</div>
27+
28+
<script>
29+
test(() => {
30+
const timeline = new ViewTimeline({ subject, axis: 'block' });
31+
assert_equals(timeline.source, outer);
32+
}, 'ViewTimeline skips non-matching ancestor scroll containers');
33+
</script>

0 commit comments

Comments
 (0)