animation-timeline-named-scroll-progress-timeline.tentative.html (13910B)
1 <!DOCTYPE html> 2 <title>The animation-timeline: scroll-timeline-name</title> 3 <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> 4 <link rel="help" src="https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-timelines-named"> 5 <link rel="help" src="https://github.com/w3c/csswg-drafts/issues/6674"> 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="support/testcommon.js"></script> 10 <script src="/scroll-animations/scroll-timelines/testcommon.js"></script> 11 <style> 12 @keyframes anim { 13 from { translate: 50px; } 14 to { translate: 150px; } 15 } 16 @keyframes anim-2 { 17 from { z-index: 0; } 18 to { z-index: 100; } 19 } 20 21 #target { 22 width: 100px; 23 height: 100px; 24 } 25 .square { 26 width: 100px; 27 height: 100px; 28 } 29 .square-container { 30 width: 300px; 31 height: 300px; 32 } 33 .scroller { 34 overflow: scroll; 35 } 36 .content { 37 inline-size: 100%; 38 block-size: 100%; 39 padding-inline-end: 100px; 40 padding-block-end: 100px; 41 } 42 </style> 43 <body> 44 <div id="log"></div> 45 <script> 46 "use strict"; 47 48 setup(assert_implements_animation_timeline); 49 50 function createScroller(t, scrollerSizeClass) { 51 let scroller = document.createElement('div'); 52 let className = scrollerSizeClass || 'square'; 53 scroller.className = `scroller ${className}`; 54 let content = document.createElement('div'); 55 content.className = 'content'; 56 57 scroller.appendChild(content); 58 59 t.add_cleanup(function() { 60 content.remove(); 61 scroller.remove(); 62 }); 63 64 return scroller; 65 } 66 67 function createTarget(t) { 68 let target = document.createElement('div'); 69 target.id = 'target'; 70 71 t.add_cleanup(function() { 72 target.remove(); 73 }); 74 75 return target; 76 } 77 78 function createScrollerAndTarget(t, scrollerSizeClass) { 79 return [createScroller(t, scrollerSizeClass), createTarget(t)]; 80 } 81 82 async function waitForScrollTop(scroller, percentage) { 83 const maxScroll = scroller.scrollHeight - scroller.clientHeight; 84 scroller.scrollTop = maxScroll * percentage / 100; 85 return waitForNextFrame(); 86 } 87 88 async function waitForScrollLeft(scroller, percentage) { 89 const maxScroll = scroller.scrollWidth - scroller.clientWidth; 90 scroller.scrollLeft = maxScroll * percentage / 100; 91 return waitForNextFrame(); 92 } 93 94 // ------------------------- 95 // Test scroll-timeline-name 96 // ------------------------- 97 98 promise_test(async t => { 99 let target = document.createElement('div'); 100 target.id = 'target'; 101 target.className = 'scroller'; 102 let content = document.createElement('div'); 103 content.className = 'content'; 104 105 await runAndWaitForFrameUpdate(() => { 106 // <div id='target' class='scroller'> 107 // <div id='content'></div> 108 // </div> 109 document.body.appendChild(target); 110 target.appendChild(content); 111 112 target.style.scrollTimelineName = '--timeline'; 113 target.style.animation = "anim 10s linear"; 114 target.style.animationTimeline = '--timeline'; 115 target.scrollTop = 50; // 50% 116 }); 117 118 assert_equals(getComputedStyle(target).translate, '100px'); 119 120 content.remove(); 121 target.remove(); 122 }, 'scroll-timeline-name is referenceable in animation-timeline on the ' + 123 'declaring element itself'); 124 125 promise_test(async t => { 126 let [parent, target] = createScrollerAndTarget(t, 'square-container'); 127 128 await runAndWaitForFrameUpdate(() => { 129 // <div id='parent' class='scroller'> 130 // <div id='target'></div> 131 // <div id='content'></div> 132 // </div> 133 document.body.appendChild(parent); 134 parent.insertBefore(target, parent.firstElementChild); 135 136 parent.style.scrollTimelineName = '--timeline'; 137 target.style.animation = "anim 10s linear"; 138 target.style.animationTimeline = '--timeline'; 139 140 parent.scrollTop = 100; // 50% 141 }); 142 143 assert_equals(getComputedStyle(target).translate, '100px'); 144 }, "scroll-timeline-name is referenceable in animation-timeline on that " + 145 "element's descendants"); 146 147 // See https://github.com/w3c/csswg-drafts/issues/7047 148 promise_test(async t => { 149 let [sibling, target] = createScrollerAndTarget(t); 150 151 await runAndWaitForFrameUpdate(() => { 152 // <div id='sibling' class='scroller'> ... </div> 153 // <div id='target'></div> 154 document.body.appendChild(sibling); 155 document.body.appendChild(target); 156 157 // Resolvable if using a deferred timeline, but otherwise can only resolve 158 // if an ancestor container of the target element. 159 sibling.style.scrollTimelineName = '--timeline'; 160 target.style.animation = "anim 10s linear"; 161 target.style.animationTimeline = '--timeline'; 162 163 sibling.scrollTop = 50; // 50% 164 }); 165 166 assert_equals(getComputedStyle(target).translate, '50px', 167 'Animation with unknown timeline name holds current time at zero'); 168 }, "scroll-timeline-name is not referenceable in animation-timeline on that " + 169 "element's siblings"); 170 171 promise_test(async t => { 172 let parent = document.createElement('div'); 173 parent.className = 'square'; 174 parent.style.overflowX = 'clip'; // This makes overflow-y be clip as well. 175 let target = document.createElement('div'); 176 target.id = 'target'; 177 178 await runAndWaitForFrameUpdate(() => { 179 // <div id='parent' style='overflow-x: clip'>... 180 // <div id='target'></div> 181 // </div> 182 document.body.appendChild(parent); 183 parent.appendChild(target); 184 185 parent.style.scrollTimelineName = '--timeline'; 186 target.style.animation = "anim 10s linear"; 187 target.style.animationTimeline = '--timeline'; 188 }); 189 190 assert_equals(getComputedStyle(target).translate, 'none', 191 'Animation with an unresolved current time'); 192 193 target.remove(); 194 parent.remove(); 195 }, 'scroll-timeline-name on an element which is not a scroll-container'); 196 197 promise_test(async t => { 198 let [scroller, target] = createScrollerAndTarget(t); 199 200 await runAndWaitForFrameUpdate(() => { 201 // <div id='scroller' class='scroller'> ... 202 // <div id='target'></div> 203 // </div> 204 205 document.body.appendChild(scroller); 206 scroller.appendChild(target); 207 208 scroller.style.scrollTimelineName = '--timeline-A'; 209 scroller.scrollTop = 50; // 25% 210 target.style.animation = "anim 10s linear"; 211 target.style.animationTimeline = '--timeline-B'; 212 }); 213 214 const anim = target.getAnimations()[0]; 215 assert_true(!!anim, 'Failed to create animation'); 216 assert_equals(anim.timeline, null); 217 // Hold time of animation is zero. 218 assert_equals(getComputedStyle(target).translate, '50px'); 219 220 scroller.style.scrollTimelineName = '--timeline-B'; 221 await waitForNextFrame(); 222 223 assert_true(!!anim.timeline, 'Failed to create timeline'); 224 assert_equals(getComputedStyle(target).translate, '75px'); 225 }, 'Change in scroll-timeline-name to match animation timeline updates animation.'); 226 227 promise_test(async t => { 228 let [scroller, target] = createScrollerAndTarget(t); 229 230 await runAndWaitForFrameUpdate(() => { 231 // <div id='scroller' class='scroller'> ... 232 // <div id='target'></div> 233 // </div> 234 235 document.body.appendChild(scroller); 236 scroller.appendChild(target); 237 238 scroller.style.scrollTimelineName = '--timeline-A'; 239 scroller.scrollTop = 50; // 25% 240 target.style.animation = "anim 10s linear"; 241 target.style.animationTimeline = '--timeline-A'; 242 }); 243 244 const anim = target.getAnimations()[0]; 245 assert_true(!!anim, 'Failed to create animation'); 246 assert_true(!!anim.timeline, 'Failed to create timeline'); 247 assert_equals(getComputedStyle(target).translate, '75px'); 248 assert_percents_equal(anim.startTime, 0); 249 assert_percents_equal(anim.currentTime, 25); 250 251 scroller.style.scrollTimelineName = '--timeline-B'; 252 await waitForNextFrame(); 253 254 // Switching to a null timeline pauses the animation. 255 assert_equals(anim.timeline, null, 'Failed to remove timeline'); 256 assert_equals(getComputedStyle(target).translate, '75px'); 257 assert_equals(anim.startTime, null); 258 assert_times_equal(anim.currentTime, 2500); 259 }, 'Change in scroll-timeline-name to no longer match animation timeline updates animation.'); 260 261 promise_test(async t => { 262 let target = createTarget(t); 263 let scroller1 = createScroller(t); 264 let scroller2 = createScroller(t); 265 266 target.style.animation = 'anim 10s linear'; 267 target.style.animationTimeline = '--timeline'; 268 scroller1.style.scrollTimelineName = '--timeline'; 269 scroller1.id = 'A'; 270 scroller2.id = 'B'; 271 272 await runAndWaitForFrameUpdate(() => { 273 // <div class='scroller' id='A'> ... 274 // <div class='scroller' id='B'> ... 275 // <div id='target'></div> 276 // </div> 277 // </div> 278 document.body.appendChild(scroller1); 279 scroller1.appendChild(scroller2); 280 scroller2.appendChild(target); 281 282 scroller1.style.scrollTimelineName = '--timeline'; 283 scroller1.scrollTop = 50; // 25% 284 scroller2.scrollTop = 100; // 50% 285 }); 286 287 const anim = target.getAnimations()[0]; 288 assert_true(!!anim.timeline, 'Failed to retrieve animation'); 289 assert_equals(anim.timeline.source.id, 'A'); 290 assert_equals(getComputedStyle(target).translate, '75px'); 291 292 scroller2.style.scrollTimelineName = '--timeline'; 293 await waitForNextFrame(); 294 295 // The timeline should be updated to scroller2. 296 assert_true(!!anim.timeline, 'Animation no longer has a timeline'); 297 assert_equals(anim.timeline.source.id, 'B', 'Timeline not updated'); 298 assert_equals(getComputedStyle(target).translate, '100px'); 299 }, 'Timeline lookup updates candidate when closer match available.'); 300 301 promise_test(async t => { 302 let wrapper = createScroller(t); 303 wrapper.classList.remove('scroller'); 304 let target = createTarget(t); 305 306 await runAndWaitForFrameUpdate(() => { 307 // <div id='wrapper'> ... 308 // <div id='target'></div> 309 // </div> 310 document.body.appendChild(wrapper); 311 wrapper.appendChild(target); 312 target.style.animation = "anim 10s linear"; 313 target.style.animationTimeline = '--timeline'; 314 }); 315 316 // Timeline initially cannot be resolved, resulting in a null 317 // timeline. The animation's hold time is zero. 318 // let anim = document.getAnimations()[0]; 319 assert_equals(getComputedStyle(target).translate, '50px'); 320 321 await runAndWaitForFrameUpdate(() => { 322 // <div id='wrapper' class="scroller"> ... 323 // <div id='target'></div> 324 // </div> 325 wrapper.classList.add('scroller'); 326 wrapper.style.scrollTimelineName = '--timeline'; 327 wrapper.scrollTop = 50; // 25% 328 }); 329 330 // The timeline should be updated to scroller. 331 assert_equals(getComputedStyle(target).translate, '75px'); 332 }, 'Timeline lookup updates candidate when match becomes available.'); 333 334 335 // ------------------------- 336 // Test scroll-timeline-axis 337 // ------------------------- 338 339 promise_test(async t => { 340 let [scroller, target] = createScrollerAndTarget(t); 341 scroller.style.writingMode = 'vertical-lr'; 342 343 await runAndWaitForFrameUpdate(() => { 344 // <div id='scroller' class='scroller'> ... 345 // <div id='target'></div> 346 // </div> 347 document.body.appendChild(scroller); 348 scroller.appendChild(target); 349 350 scroller.style.scrollTimeline = '--timeline block'; 351 target.style.animation = "anim-2 10s linear"; 352 target.style.animationTimeline = '--timeline'; 353 }); 354 355 await waitForScrollLeft(scroller, 50); 356 assert_equals(getComputedStyle(target).zIndex, '50'); 357 }, 'scroll-timeline-axis is block'); 358 359 promise_test(async t => { 360 let [scroller, target] = createScrollerAndTarget(t); 361 scroller.style.writingMode = 'vertical-lr'; 362 363 await runAndWaitForFrameUpdate(() => { 364 // <div id='scroller' class='scroller'> ... 365 // <div id='target'></div> 366 // </div> 367 document.body.appendChild(scroller); 368 scroller.appendChild(target); 369 370 scroller.style.scrollTimeline = '--timeline inline'; 371 target.style.animation = "anim-2 10s linear"; 372 target.style.animationTimeline = '--timeline'; 373 }); 374 375 await waitForScrollTop(scroller, 50); 376 assert_equals(getComputedStyle(target).zIndex, '50'); 377 }, 'scroll-timeline-axis is inline'); 378 379 promise_test(async t => { 380 let [scroller, target] = createScrollerAndTarget(t); 381 scroller.style.writingMode = 'vertical-lr'; 382 383 await runAndWaitForFrameUpdate(() => { 384 // <div id='scroller' class='scroller'> ... 385 // <div id='target'></div> 386 // </div> 387 document.body.appendChild(scroller); 388 scroller.appendChild(target); 389 390 scroller.style.scrollTimeline = '--timeline x'; 391 target.style.animation = "anim-2 10s linear"; 392 target.style.animationTimeline = '--timeline'; 393 }); 394 395 await waitForScrollLeft(scroller, 50); 396 assert_equals(getComputedStyle(target).zIndex, '50'); 397 }, 'scroll-timeline-axis is x'); 398 399 promise_test(async t => { 400 let [scroller, target] = createScrollerAndTarget(t); 401 scroller.style.writingMode = 'vertical-lr'; 402 403 await runAndWaitForFrameUpdate(() => { 404 // <div id='scroller' class='scroller'> ... 405 // <div id='target'></div> 406 // </div> 407 document.body.appendChild(scroller); 408 scroller.appendChild(target); 409 410 scroller.style.scrollTimeline = '--timeline y'; 411 target.style.animation = "anim-2 10s linear"; 412 target.style.animationTimeline = '--timeline'; 413 }); 414 415 await waitForScrollTop(scroller, 50); 416 assert_equals(getComputedStyle(target).zIndex, '50'); 417 }, 'scroll-timeline-axis is y'); 418 419 promise_test(async t => { 420 let [scroller, target] = createScrollerAndTarget(t); 421 422 await runAndWaitForFrameUpdate(() => { 423 // <div id='scroller' class='scroller'> ... 424 // <div id='target'></div> 425 // </div> 426 document.body.appendChild(scroller); 427 scroller.appendChild(target); 428 429 scroller.style.scrollTimeline = '--timeline block'; 430 target.style.animation = "anim-2 10s linear"; 431 target.style.animationTimeline = '--timeline'; 432 }); 433 434 await waitForScrollTop(scroller, 25); 435 await waitForScrollLeft(scroller, 75); 436 assert_equals(getComputedStyle(target).zIndex, '25'); 437 438 scroller.style.scrollTimelineAxis = 'inline'; 439 await waitForNextFrame(); 440 assert_equals(getComputedStyle(target).zIndex, '75'); 441 }, 'scroll-timeline-axis is mutated'); 442 443 </script> 444 </body>