scroll-timeline-invalidation.html (5252B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>ScrollTimeline invalidation</title> 4 <link rel="help" href="https://wicg.github.io/scroll-animations/#current-time-algorithm"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="/web-animations/testcommon.js"></script> 8 <script src="testcommon.js"></script> 9 <style> 10 .scroller { 11 overflow: auto; 12 height: 100px; 13 width: 100px; 14 will-change: transform; 15 } 16 .contents { 17 height: 1000px; 18 width: 100%; 19 } 20 </style> 21 <div id="log"></div> 22 23 <script> 24 'use strict'; 25 26 promise_test(async t => { 27 const animation = createScrollLinkedAnimation(t); 28 const effect_duration = 350; 29 animation.effect.updateTiming({ duration: effect_duration }); 30 const scroller = animation.timeline.source; 31 let maxScroll = scroller.scrollHeight - scroller.clientHeight; 32 scroller.scrollTop = 0.2 * maxScroll; 33 const initial_progress = (scroller.scrollTop / maxScroll) * 100; 34 35 animation.play(); 36 await animation.ready; 37 38 // Animation current time is at 20% because scroller was scrolled to 20% 39 assert_percents_equal(animation.currentTime, 20); 40 assert_equals(scroller.scrollTop, 180); 41 assert_equals(maxScroll, 900); 42 43 // Shrink scroller content size (from 1000 to 500). 44 // scroller.scrollTop maintains the same offset, which means shrinking the 45 // content has the effect of skipping the animation forward. 46 scroller.firstChild.style.height = "500px"; 47 maxScroll = scroller.scrollHeight - scroller.clientHeight; 48 49 assert_equals(scroller.scrollTop, 180); 50 assert_equals(maxScroll, 400); 51 await waitForNextFrame(); 52 53 const expected_progress = (scroller.scrollTop / maxScroll) * 100; 54 assert_true(expected_progress > initial_progress) 55 // @ 45% 56 assert_percents_equal(animation.currentTime, expected_progress); 57 assert_percents_equal(animation.timeline.currentTime, expected_progress); 58 assert_percents_equal(animation.effect.getComputedTiming().localTime, expected_progress); 59 }, 'Animation current time and effect local time are updated after scroller ' + 60 'content size changes.'); 61 62 promise_test(async t => { 63 const animation = createScrollLinkedAnimation(t); 64 animation.effect.updateTiming({ duration: 350 }); 65 const scroller = animation.timeline.source; 66 let maxScroll = scroller.scrollHeight - scroller.clientHeight; 67 const scrollOffset = 0.2 * maxScroll 68 scroller.scrollTop = scrollOffset; 69 const initial_progress = (scroller.scrollTop / maxScroll) * 100; 70 71 animation.play(); 72 await animation.ready; 73 74 // Animation current time is at 20% because scroller was scrolled to 20% 75 // assert_equals(animation.currentTime.value, 20); 76 assert_percents_equal(animation.currentTime, 20); 77 assert_equals(scroller.scrollTop, scrollOffset); 78 assert_equals(maxScroll, 900); 79 80 // Change scroller size. 81 scroller.style.height = "500px"; 82 maxScroll = scroller.scrollHeight - scroller.clientHeight; 83 84 assert_equals(scroller.scrollTop, scrollOffset); 85 assert_equals(maxScroll, 500); 86 await waitForNextFrame(); 87 88 const expected_progress = (scroller.scrollTop / maxScroll) * 100; 89 assert_true(expected_progress > initial_progress); 90 // @ 45% 91 assert_percents_equal(animation.currentTime, expected_progress); 92 assert_percents_equal(animation.timeline.currentTime, expected_progress); 93 assert_percents_equal(animation.effect.getComputedTiming().localTime, 94 expected_progress); 95 }, 'Animation current time and effect local time are updated after scroller ' + 96 'size changes.'); 97 98 promise_test(async t => { 99 await waitForNextFrame(); 100 101 const timeline = createScrollTimeline(t); 102 const scroller = timeline.source; 103 const maxScroll = scroller.scrollHeight - scroller.clientHeight; 104 // Instantiate scroll animation that resizes its scroll timeline scroller. 105 const animation = new Animation( 106 new KeyframeEffect( 107 timeline.source.firstChild, 108 [{ height: '1000px', easing: 'steps(2, jump-none)'}, 109 { height: '2000px' }], 110 ), timeline); 111 112 animation.play(); 113 await animation.ready; 114 115 assert_percents_equal(timeline.currentTime, 0); 116 assert_equals(scroller.scrollHeight, 1000); 117 118 await runAndWaitForFrameUpdate(() => { 119 scroller.scrollTop = 0.6 * maxScroll; 120 }); 121 122 // Applying the animation effect alters the height of the scroll content and 123 // makes the scroll timeline stale. 124 // https://github.com/w3c/csswg-drafts/issues/8694 125 126 // runAndWaitForFrameUpdate will yield after the requestAnimationFrame callbacks 127 // have been serviced, which is prior to when stale timelines are updated. 128 // https://github.com/w3c/csswg-drafts/issues/12120 129 assert_approx_equals(timeline.currentTime.value, 60, 0.1); 130 131 // Now wait another beat such that the rest of the HTML Processing Model event loop 132 // has run and we can check whether stale timelines have been updated. 133 await new Promise(setTimeout); 134 135 // With a single layout, timeline current time would be at 60%, but the 136 // timeline would be stale. 137 const expected_progress = 60 * maxScroll / (maxScroll + 1000); 138 assert_approx_equals(timeline.currentTime.value, expected_progress, 0.1); 139 140 }, 'If scroll animation resizes its scroll timeline scroller, ' + 141 'layout reruns once per frame.'); 142 </script>