scroll-timeline-dynamic.tentative.html (8817B)
1 <!DOCTYPE html> 2 <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> 3 <link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timelines"> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="/web-animations/testcommon.js"></script> 7 <script src="support/testcommon.js"></script> 8 <style> 9 main { 10 timeline-scope: --timeline; 11 } 12 13 main > div { 14 overflow: hidden; 15 width: 100px; 16 height: 100px; 17 } 18 main > div > div { 19 height: 200px; 20 } 21 @keyframes expand { 22 from { width: 100px; } 23 to { width: 200px; } 24 } 25 #element { 26 width: 0px; 27 height: 20px; 28 animation-name: expand; 29 /* Some of the tests in this file assume animations attached to the 30 DocumentTimeline are "stopped" without actually being paused. 31 Using 600s + steps(10, end) achieves this for one minute.*/ 32 animation-duration: 600s; 33 animation-timing-function: steps(10, end); 34 } 35 </style> 36 <main id=main> 37 <div id=scroller1 class=scroller> 38 <div></div> 39 </div> 40 <div id=scroller2 class=scroller> 41 <div></div> 42 </div> 43 <div id=container></div> 44 </main> 45 <script> 46 // Force layout of scrollers. 47 scroller1.offsetTop; 48 scroller2.offsetTop; 49 50 // Note the steps(10, end) timing function and height:100px. (10px scroll 51 // resolution). 52 scroller1.scrollTop = 20; 53 scroller2.scrollTop = 40; 54 55 function insertElement() { 56 let element = document.createElement('div'); 57 element.id = 'element'; 58 container.append(element); 59 return element; 60 } 61 62 // Runs a test with dynamically added/removed elements or CSS rules. 63 // Each test is instantiated twice: once for the initial style resolve where 64 // the DOM change was effectuated, and once after scrolling. 65 function dynamic_rule_test(func, description) { 66 // assert_width is an async function which verifies that the computed value 67 // of 'width' is as expected. 68 const instantiate = (assert_width, description) => { 69 promise_test(async (t) => { 70 try { 71 await func(t, assert_width); 72 } finally { 73 while (container.firstChild) 74 container.firstChild.remove(); 75 main.style = ''; 76 scroller1.style = ''; 77 scroller2.style = ''; 78 } 79 }, description); 80 }; 81 82 // Verify that the computed style is as expected after a full frame update 83 // following the rule change took place. 84 instantiate(async (element, expected) => { 85 await waitForCSSScrollTimelineStyle(); 86 assert_equals(getComputedStyle(element).width, expected); 87 }, description + ' [immediate]'); 88 89 // Verify the computed style after scrolling a bit. 90 instantiate(async (element, expected) => { 91 await waitForNextFrame(); 92 scroller1.scrollTop = scroller1.scrollTop + 10; 93 scroller2.scrollTop = scroller2.scrollTop + 10; 94 await waitForNextFrame(); 95 scroller1.scrollTop = scroller1.scrollTop - 10; 96 scroller2.scrollTop = scroller2.scrollTop - 10; 97 await waitForNextFrame(); 98 assert_equals(getComputedStyle(element).width, expected); 99 }, description + ' [scroll]'); 100 } 101 102 dynamic_rule_test(async (t, assert_width) => { 103 let element = insertElement(); 104 105 // This element initially has a DocumentTimeline. 106 await assert_width(element, '100px'); 107 108 // Switch to scroll timeline. 109 scroller1.style.scrollTimelineName = '--timeline'; 110 element.style.animationTimeline = '--timeline'; 111 await assert_width(element, '120px'); 112 113 // Switching from ScrollTimeline -> DocumentTimeline should preserve 114 // current time. 115 scroller1.style = ''; 116 element.style = ''; 117 await assert_width(element, '120px'); 118 }, 'Switching between document and scroll timelines'); 119 120 dynamic_rule_test(async (t, assert_width) => { 121 let element = insertElement(); 122 123 // Flush style and create the animation with play pending. 124 getComputedStyle(element).animation; 125 126 let anim = element.getAnimations()[0]; 127 assert_true(anim.pending, "The animation is in play pending"); 128 129 // Switch to scroll timeline for a pending animation. 130 scroller1.style.scrollTimelineName = '--timeline'; 131 element.style.animationTimeline = '--timeline'; 132 133 await anim.ready; 134 assert_false(anim.pending, "The animation is not pending"); 135 136 await assert_width(element, '120px'); 137 }, 'Switching pending animation from document to scroll timelines'); 138 139 dynamic_rule_test(async (t, assert_width) => { 140 let element = insertElement(); 141 142 // Note: #scroller1 is at 20%, and #scroller2 is at 40%. 143 scroller1.style.scrollTimelineName = '--timeline1'; 144 scroller2.style.scrollTimelineName = '--timeline2'; 145 main.style.timelineScope = "--timeline1, --timeline2"; 146 147 await assert_width(element, '100px'); 148 149 element.style.animationTimeline = '--timeline1'; 150 await assert_width(element, '120px'); 151 152 element.style.animationTimeline = '--timeline2'; 153 await assert_width(element, '140px'); 154 155 element.style.animationTimeline = '--timeline1'; 156 await assert_width(element, '120px'); 157 158 // Switching from ScrollTimeline -> DocumentTimeline should preserve 159 // current time. 160 element.style.animationTimeline = ''; 161 await assert_width(element, '120px'); 162 163 }, 'Changing computed value of animation-timeline changes effective timeline'); 164 165 dynamic_rule_test(async (t, assert_width) => { 166 let element = insertElement(); 167 168 scroller1.style.scrollTimelineName = '--timeline'; 169 170 // DocumentTimeline applies by default. 171 await assert_width(element, '100px'); 172 173 // Wait for the animation to be ready so that we a start time and no hold 174 // time. 175 await element.getAnimations()[0].ready; 176 177 // DocumentTimeline -> none 178 element.style.animationTimeline = '--none'; 179 await assert_width(element, '0px'); 180 181 // none -> DocumentTimeline 182 element.style.animationTimeline = ''; 183 await assert_width(element, '100px'); 184 185 // DocumentTimeline -> ScrollTimeline 186 element.style.animationTimeline = '--timeline'; 187 await assert_width(element, '120px'); 188 189 // ScrollTimeline -> none 190 element.style.animationTimeline = '--none'; 191 await assert_width(element, '120px'); 192 193 // none -> ScrollTimeline 194 element.style.animationTimeline = '--timeline'; 195 await assert_width(element, '120px'); 196 }, 'Changing to/from animation-timeline:none'); 197 198 199 dynamic_rule_test(async (t, assert_width) => { 200 let element = insertElement(); 201 202 element.style.animationDirection = 'reverse'; 203 element.style.animationTimeline = '--timeline'; 204 205 // Inactive animation-timeline. Animation is inactive. 206 await assert_width(element, '0px'); 207 208 // Note: #scroller1 is at 20%. 209 scroller1.style.scrollTimelineName = '--timeline'; 210 await assert_width(element, '180px'); 211 212 // Note: #scroller2 is at 40%. 213 scroller1.style.scrollTimelineName = ''; 214 scroller2.style.scrollTimelineName = '--timeline'; 215 await assert_width(element, '160px'); 216 217 element.style.animationDirection = ''; 218 await assert_width(element, '140px'); 219 }, 'Reverse animation direction'); 220 221 dynamic_rule_test(async (t, assert_width) => { 222 let element = insertElement(); 223 element.style.animationTimeline = '--timeline'; 224 225 // Inactive animation-timeline. Animation effect is inactive. 226 await assert_width(element, '0px'); 227 228 // Note: #scroller1 is at 20%. 229 scroller1.style.scrollTimelineName = '--timeline'; 230 await assert_width(element, '120px'); 231 232 element.style.animationPlayState = 'paused'; 233 234 // We should still be at the same position after pausing. 235 await assert_width(element, '120px'); 236 237 // Note: #scroller2 is at 40%. 238 scroller1.style.scrollTimelineName = ''; 239 scroller2.style.scrollTimelineName = '--timeline'; 240 241 // Should be at the same position until we unpause. 242 await assert_width(element, '120px'); 243 244 // Unpausing should synchronize to the scroll position. 245 element.style.animationPlayState = ''; 246 await assert_width(element, '140px'); 247 }, 'Change to timeline attachment while paused'); 248 249 dynamic_rule_test(async (t, assert_width) => { 250 let element = insertElement(); 251 252 // Note: #scroller1 is at 20%. 253 scroller1.style.scrollTimelineName = '--timeline'; 254 255 await assert_width(element, '100px'); 256 257 element.style.animationTimeline = '--timeline'; 258 element.style.animationPlayState = 'paused'; 259 260 // Pausing should happen after the timeline was modified. 261 // https://github.com/w3c/csswg-drafts/issues/5653 262 await assert_width(element, '120px'); 263 264 // Changing the play state should not change the animation current time. 265 element.style.animationPlayState = 'running'; 266 await assert_width(element, '120px'); 267 }, 'Switching timelines and pausing at the same time'); 268 </script>