| <!DOCTYPE html> |
| <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> |
| <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> |
| <script src="support/testcommon.js"></script> |
| <style> |
| main { |
| timeline-scope: --timeline; |
| } |
| |
| 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 id=main> |
| <div id=scroller1 class=scroller> |
| <div></div> |
| </div> |
| <div id=scroller2 class=scroller> |
| <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(); |
| main.style = ''; |
| scroller1.style = ''; |
| scroller2.style = ''; |
| } |
| }, description); |
| }; |
| |
| // Verify that the computed style is as expected after a full frame update |
| // following the rule change took place. |
| instantiate(async (element, expected) => { |
| await waitForCSSScrollTimelineStyle(); |
| assert_equals(getComputedStyle(element).width, expected); |
| }, description + ' [immediate]'); |
| |
| // Verify the computed style after scrolling a bit. |
| instantiate(async (element, expected) => { |
| await waitForNextFrame(); |
| 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(); |
| |
| // Flush style and create the animation with play pending. |
| getComputedStyle(element).animation; |
| |
| let anim = element.getAnimations()[0]; |
| assert_true(anim.pending, "The animation is in play pending"); |
| |
| // Switch to scroll timeline for a pending animation. |
| scroller1.style.scrollTimelineName = '--timeline'; |
| element.style.animationTimeline = '--timeline'; |
| |
| await anim.ready; |
| assert_false(anim.pending, "The animation is not pending"); |
| |
| await assert_width(element, '120px'); |
| }, 'Switching pending animation from document to 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'; |
| main.style.timelineScope = "--timeline1, --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'); |
| |
| // Wait for the animation to be ready so that we a start time and no hold |
| // time. |
| await element.getAnimations()[0].ready; |
| |
| // 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, '120px'); |
| |
| // 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.animationDirection = 'reverse'; |
| element.style.animationTimeline = '--timeline'; |
| |
| // Inactive animation-timeline. Animation is inactive. |
| await assert_width(element, '0px'); |
| |
| // Note: #scroller1 is at 20%. |
| scroller1.style.scrollTimelineName = '--timeline'; |
| await assert_width(element, '180px'); |
| |
| // Note: #scroller2 is at 40%. |
| scroller1.style.scrollTimelineName = ''; |
| 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'; |
| |
| // Inactive animation-timeline. Animation effect is inactive. |
| await assert_width(element, '0px'); |
| |
| // 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%. |
| scroller1.style.scrollTimelineName = ''; |
| scroller2.style.scrollTimelineName = '--timeline'; |
| |
| // 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'); |
| }, 'Change to timeline attachment 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> |