Skip to content

Commit

Permalink
[scroll-animations] Integrate @scroll-timeline with animations
Browse files Browse the repository at this point in the history
Until now it was possible to parse @scroll-timeline rules, but they
did not actually do anything. This CL performs the necessary
integration into StyleEngine and CSSAnimations (partially) to make
something happen.

The applicable StyleRuleScrollTimeline objects are maintained on
StyleEngine. When calculating the animation update, we now take
the animation-timeline property into account, and look for a
rule on StyleEngine corresponding to the specified timeline name.
This rule (if found) then causes a new ScrollTimeline to be created.

This is by no means a fully functional implementation, but it's a
start. The most notable omission is updating timelines, which is
currently not possible (crbug.com/1097053).

Other omissions, flaws, or postponed work are either tracked as
bugs (with TODOs that reference them) or as failing expectations.
This ensures that we don't miss anything before shipping.

Bug:1074052
Change-Id: I04e75be1dc970ed2f542e60457f8db25bbb0fe7a
  • Loading branch information
andruud authored and chromium-wpt-export-bot committed Jul 10, 2020
1 parent e2ebd58 commit efdf6d4
Show file tree
Hide file tree
Showing 9 changed files with 750 additions and 0 deletions.
62 changes: 62 additions & 0 deletions scroll-animations/css/at-scroll-timeline-before-phase.html
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#phase-algorithm">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<style>
#scroller {
overflow: scroll;
width: 100px;
height: 100px;
}
#contents {
height: 200px;
}
@keyframes expand {
from { width: 100px; }
to { width: 200px; }
}
#element {
width: 0px;
}
/* Ensure stable expectations if feature is not supported */
@supports not (animation-timeline:foo) {
#element { animation-play-state: paused; }
}
</style>
<div id=scroller>
<div id=contents></div>
</div>
<div id=container></div>
<script>
promise_test(async (t) => {
try {
// Make sure scroller has a layout box.
await waitForNextFrame();

container.innerHTML = `
<div id=element></div>
<style>
@scroll-timeline timeline {
source: selector(#scroller);
time-range: 10s;
start: 50px;
end: 100px;
}
#element {
animation: expand 10s linear;
animation-timeline: timeline;
}
</style>
`;
// Animation should not apply in before phase.
assert_equals(getComputedStyle(element).width, '0px');
await waitForNextFrame();
// Animation should still not apply.
assert_equals(getComputedStyle(element).width, '0px');
} finally {
container.innerHTML = '';
}
}, 'Animation does not apply when timeline phase is before');
</script>
50 changes: 50 additions & 0 deletions scroll-animations/css/at-scroll-timeline-cascade.html
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<style>
#scroller {
overflow: scroll;
width: 100px;
height: 100px;
}
#contents {
height: 200px;
}
@keyframes expand {
from { width: 100px; }
to { width: 200px; }
}
@scroll-timeline timeline {
source: selector(#scroller);
time-range: 10s;
start: 0px;
end: 100px;
}
@scroll-timeline timeline {
source: selector(#scroller);
time-range: 1s;
start: 0px;
end: 50px;
}
#element {
animation: expand 10s linear;
animation-timeline: timeline;
}
/* Ensure stable expectations if feature is not supported */
@supports not (animation-timeline:foo) {
#element { animation-play-state: paused; }
}
</style>
<div id=scroller>
<div id=contents></div>
</div>
<div id=element></div>
<script>
promise_test(async (t) => {
scroller.scrollTop = 25;
await waitForNextFrame();
assert_equals(getComputedStyle(element).width, '105px');
}, 'Latest @scroll-timeline rule wins');
</script>
59 changes: 59 additions & 0 deletions scroll-animations/css/at-scroll-timeline-inactive-phase.html
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#phase-algorithm">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<style>
#scroller {
overflow: scroll;
width: 100px;
height: 100px;
}
#contents {
height: 200px;
}
@keyframes expand {
from { width: 100px; }
to { width: 200px; }
}
#element {
width: 0px;
animation: expand 10s linear paused;
animation-timeline: timeline;
}
</style>
<div id="container"></div>
<div id=element></div>
<script>

promise_test(async (t) => {
try {
container.innerHTML = `
<div id=scroller>
<div id=contents></div>
</div>
<style>
@scroll-timeline timeline {
source: selector(#scroller);
time-range: 10s;
start: 0px;
end: 100px;
}
</style>
`;

// The source has no layout box at the time the scroll timeline is created.
assert_equals(getComputedStyle(element).width, '0px');
scroller.offsetTop; // Ensure a layout box for the scroller.
// Wait for an update to the timeline state:
await waitForNextFrame();
// The timeline should now be active, and the animation should apply:
assert_equals(getComputedStyle(element).width, '100px');
} finally {
container.innerHTML = '';
}
}, 'Animation does not apply when timeline is initially inactive');

</script>
129 changes: 129 additions & 0 deletions scroll-animations/css/at-scroll-timeline-orientation.html
@@ -0,0 +1,129 @@
<!DOCTYPE html>
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#descdef-scroll-timeline-orientation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<style>
#scroller {
overflow: scroll;
width: 100px;
height: 100px;
}
#contents {
height: 300px;
width: 300px;
}
@keyframes expand {
from { width: 100px; }
to { width: 200px; }
}
@scroll-timeline timeline_auto {
source: selector(#scroller);
orientation: auto;
time-range: 10s;
start: 0px;
end: 100px;
}
@scroll-timeline timeline_vertical {
source: selector(#scroller);
orientation: vertical;
time-range: 10s;
start: 0px;
end: 100px;
}
@scroll-timeline timeline_horizontal {
source: selector(#scroller);
orientation: horizontal;
time-range: 10s;
start: 0px;
end: 100px;
}
@scroll-timeline timeline_block {
source: selector(#scroller);
orientation: block;
time-range: 10s;
start: 0px;
end: 100px;
}
@scroll-timeline timeline_inline {
source: selector(#scroller);
orientation: inline;
time-range: 10s;
start: 0px;
end: 100px;
}
#container > div {
width: 0px;
animation: expand 10s linear;
}
/* Ensure stable expectations if feature is not supported */
@supports not (animation-timeline:foo) {
#container > div { animation-play-state: paused; }
}
.horizontal { writing-mode: horizontal-tb; }
.vertical { writing-mode: vertical-lr; }
#element_auto { animation-timeline: timeline_auto; }
#element_vertical { animation-timeline: timeline_vertical; }
#element_horizontal { animation-timeline: timeline_horizontal; }
#element_block_in_horizontal { animation-timeline: timeline_block; }
#element_inline_in_horizontal { animation-timeline: timeline_inline; }
#element_block_in_vertical { animation-timeline: timeline_block; }
#element_inline_in_vertical { animation-timeline: timeline_inline; }
</style>
<div id=scroller>
<div id=contents></div>
</div>
<div id=container>
<div id=element_auto></div>
<div id=element_vertical></div>
<div id=element_horizontal></div>
<div id=element_block_in_horizontal class="horizontal"></div>
<div id=element_inline_in_horizontal class="horizontal"></div>
<div id=element_block_in_vertical class="vertical"></div>
<div id=element_inline_in_vertical class="vertical"></div>
</div>
<script>
// Animations linked to a vertical scroll-timelines are at 75% progress.
scroller.scrollTop = 75;
// Animations linked to a horizontal scroll-timelines are at 25% progress.
scroller.scrollLeft = 25;

promise_test(async (t) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element_auto).width, '175px');
}, 'Orientation auto behaves as expected');

promise_test(async (t) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element_vertical).width, '175px');
}, 'Orientation vertical behaves as expected');

promise_test(async (t) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element_horizontal).width, '125px');
}, 'Orientation horizontal behaves as expected');

promise_test(async (t) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element_block_in_horizontal).width, '175px');
}, 'Orientation block behaves as expected in horizontal writing-mode');

promise_test(async (t) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element_inline_in_horizontal).width, '125px');
}, 'Orientation inline behaves as expected in horizontal writing-mode');

promise_test(async (t) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element_block_in_vertical).writingMode, 'vertical-lr');
assert_equals(getComputedStyle(element_block_in_vertical).width, '125px');
}, 'Orientation block behaves as expected in vertical writing-mode');

promise_test(async (t) => {
await waitForNextFrame();
assert_equals(getComputedStyle(element_inline_in_vertical).writingMode, 'vertical-lr');
assert_equals(getComputedStyle(element_inline_in_vertical).width, '175px');
}, 'Orientation inline behaves as expected in vertical writing-mode');

</script>
52 changes: 52 additions & 0 deletions scroll-animations/css/at-scroll-timeline-sampling.html
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<style>
#scroller {
overflow: scroll;
width: 100px;
height: 100px;
}
#contents {
height: 200px;
}
@keyframes expand {
from { width: 100px; }
to { width: 200px; }
}
@scroll-timeline timeline {
source: selector(#scroller);
time-range: 10s;
start: 0px;
end: 100px;
}
#element {
width: 0px;
animation: expand 10s linear;
animation-timeline: timeline;
}
/* Ensure stable expectations if feature is not supported */
@supports not (animation-timeline:foo) {
#element { animation-play-state: paused; }
}
</style>
<div id=scroller>
<div id=contents></div>
</div>
<div id=element></div>
<script>
promise_test(async (t) => {
// The scroll timeline is initially inactive until the first frame.
assert_equals(getComputedStyle(element).width, '0px');
await waitForNextFrame();
scroller.scrollTop = 50;
// Scrolling position should not yet be reflected in the animation,
// since the new scroll position has not yet been sampled.
assert_equals(getComputedStyle(element).width, '100px');
await waitForNextFrame();
assert_equals(getComputedStyle(element).width, '150px');
}, 'Scroll position is sampled once per frame');
</script>

0 comments on commit efdf6d4

Please sign in to comment.