| <!DOCTYPE html> |
| <link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines"> |
| <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> |
| @keyframes expand { |
| from { width: 100px; } |
| to { width: 200px; } |
| } |
| .scroller { |
| overflow: scroll; |
| width: 100px; |
| height: 100px; |
| } |
| #main { |
| timeline-scope: --timeline; |
| } |
| </style> |
| <main id=main></main> |
| <script> |
| function inflate(t, template) { |
| t.add_cleanup(() => main.replaceChildren()); |
| main.append(template.content.cloneNode(true)); |
| main.offsetTop; |
| } |
| </script> |
| |
| <template id=basic> |
| <style> |
| #timeline { |
| scroll-timeline: --timeline; |
| } |
| #element { |
| width: 0px; |
| animation: expand; |
| animation-timeline: --timeline; |
| } |
| </style> |
| <div id="container"> |
| <div id=timeline class=scroller><div> |
| <div id=element></div> |
| </div> |
| </template> |
| <script> |
| promise_test(async (t) => { |
| await runAndWaitForFrameUpdate(() => { |
| inflate(t, basic); |
| }); |
| const animation = document.getAnimations()[0]; |
| assert_true(animation.timeline instanceof ScrollTimeline, |
| 'Timeline successfully set'); |
| assert_equals(getComputedStyle(element).width, '0px'); |
| }, 'Animation does not apply when the timeline is inactive because there is ' + |
| 'no scroll range'); |
| </script> |
| |
| <template id=dynamically_change_range> |
| <style> |
| #timeline { |
| scroll-timeline: --timeline; |
| } |
| #contents { |
| height: 200px; |
| } |
| #contents.shrink { |
| height: 0px; |
| } |
| #element { |
| width: 0px; |
| animation: expand; |
| animation-timeline: --timeline; |
| } |
| </style> |
| <div id="container"> |
| <div id=timeline class=scroller> |
| <div id="contents"></div> |
| </div> |
| <div id=element></div> |
| </div> |
| </template> |
| <script> |
| promise_test(async (t) => { |
| let scroller; |
| let initial_width; |
| await runAndWaitForFrameUpdate(() => { |
| inflate(t, dynamically_change_range); |
| scroller = document.querySelector('.scroller'); |
| scroller.scrollTop = 0; |
| initial_width = getComputedStyle(element).width; |
| }); |
| assert_equals(initial_width, '0px', |
| 'Animation not in effect until after next animation frame'); |
| const animation = document.getAnimations()[0]; |
| assert_true(animation.timeline instanceof ScrollTimeline, |
| 'Timeline successfully set'); |
| assert_equals(getComputedStyle(element).width, '100px', |
| 'Style after frame update'); |
| |
| let updated_width; |
| let updated_scroll_range; |
| await runAndWaitForFrameUpdate(() => { |
| // Style change after the first update-cycle for the frame. |
| // timeline time remains fixed until the next frame. |
| contents.classList.add('shrink'); |
| updated_width = getComputedStyle(element).width; |
| udpated_scroll_range = scroller.scrollHeight - scroller.clientHeight; |
| }); |
| assert_equals(updated_width, '100px', |
| 'Animation progress fixed until next animation frame'); |
| assert_equals(udpated_scroll_range, 0, |
| 'Scroll range after content collapses'); |
| // Timeline is once again inactive. |
| assert_equals(getComputedStyle(element).width, '0px', |
| 'Timeline time after frame update with zero scroll range'); |
| |
| }, 'Animation does not apply when timeline becomes inactive'); |
| </script> |