scroll-timeline-range-animation.html (5154B)
1 <!DOCTYPE html> 2 <title>Scroll timelines and animation attachment ranges</title> 3 <link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range"> 4 <link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#animation-range"> 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="support/testcommon.js"></script> 9 <style> 10 @keyframes anim { 11 from { z-index: 0; background-color: skyblue;} 12 to { z-index: 100; background-color: coral; } 13 } 14 #scroller { 15 border: 10px solid lightgray; 16 overflow-y: scroll; 17 width: 200px; 18 height: 200px; 19 } 20 #scroller > div { 21 margin: 800px 0px; 22 width: 100px; 23 height: 100px; 24 } 25 #target { 26 font-size: 10px; 27 background-color: green; 28 z-index: -1; 29 } 30 </style> 31 <main id=main> 32 </main> 33 34 <template id=template_without_scope> 35 <div id=scroller class=timeline> 36 <div id=target></div> 37 </div> 38 </template> 39 40 <template id=template_with_scope> 41 <div id=scope> 42 <div id=target></div> 43 <div id=scroller class=timeline> 44 <div></div> 45 </div> 46 </div> 47 </template> 48 49 <script> 50 setup(assert_implements_animation_timeline); 51 52 function inflate(t, template) { 53 t.add_cleanup(() => main.replaceChildren()); 54 main.append(template.content.cloneNode(true)); 55 } 56 async function scrollTop(e, value) { 57 e.scrollTop = value; 58 await waitForNextFrame(); 59 } 60 async function waitForAnimationReady(target) { 61 await waitForNextFrame(); 62 await Promise.all(target.getAnimations().map(x => x.ready)); 63 } 64 async function assertValueAt(scroller, target, args) { 65 await waitForAnimationReady(target); 66 await scrollTop(scroller, args.scrollTop); 67 assert_equals(getComputedStyle(target).zIndex, args.expected.toString()); 68 } 69 function test_animation_range(options, template, desc_suffix) { 70 if (template === undefined) 71 template = template_without_scope; 72 if (desc_suffix === undefined) 73 desc_suffix = ''; 74 75 promise_test(async (t) => { 76 inflate(t, template); 77 let scroller = main.querySelector('#scroller'); 78 let target = main.querySelector('#target'); 79 let timeline = main.querySelector('.timeline'); 80 let scope = main.querySelector('#scope'); 81 let maxScroll = scroller.scrollHeight - scroller.clientHeight; 82 83 if (scope != null) { 84 scope.style.timelineScope = '--t1'; 85 } 86 87 timeline.style.scrollTimeline = '--t1'; 88 target.style.animation = 'anim auto linear'; 89 target.style.animationTimeline = '--t1'; 90 target.style.animationRangeStart = options.rangeStart; 91 target.style.animationRangeEnd = options.rangeEnd; 92 93 // Accommodates floating point precision errors at the endpoints. 94 target.style.animationFillMode = 'both'; 95 96 // 0% 97 await assertValueAt(scroller, target, 98 { scrollTop: options.startOffset, expected: 0 }); 99 // 50% 100 await assertValueAt(scroller, target, 101 { scrollTop: (options.startOffset + options.endOffset) / 2, expected: 50 }); 102 // 100% 103 await assertValueAt(scroller, target, 104 { scrollTop: options.endOffset, expected: 100 }); 105 106 // Test before/after phases (need to clear the fill mode for that). 107 target.style.animationFillMode = 'initial'; 108 let before_scroll = options.startOffset - 10; 109 if (before_scroll >= 0) { 110 await assertValueAt(scroller, target, 111 { scrollTop: options.startOffset - 10, expected: -1 }); 112 } 113 let after_scroll = options.startOffset + 10; 114 if (after_scroll <= scroller.maxmum) { 115 await assertValueAt(scroller, target, 116 { scrollTop: options.endOffset + 10, expected: -1 }); 117 } 118 // Check 50% again without fill mode. 119 await assertValueAt(scroller, target, 120 { scrollTop: (options.startOffset + options.endOffset) / 2, expected: 50 }); 121 122 }, `Animation with ranges [${options.rangeStart}, ${options.rangeEnd}] ${desc_suffix}`.trim()); 123 } 124 125 test_animation_range({ 126 rangeStart: 'initial', 127 rangeEnd: 'initial', 128 startOffset: 0, 129 endOffset: 1500 130 }); 131 132 test_animation_range({ 133 rangeStart: '0%', 134 rangeEnd: '100%', 135 startOffset: 0, 136 endOffset: 1500 137 }); 138 139 test_animation_range({ 140 rangeStart: '10%', 141 rangeEnd: '100%', 142 startOffset: 150, 143 endOffset: 1500 144 }); 145 146 test_animation_range({ 147 rangeStart: '0%', 148 rangeEnd: '50%', 149 startOffset: 0, 150 endOffset: 750 151 }); 152 153 test_animation_range({ 154 rangeStart: '10%', 155 rangeEnd: '50%', 156 startOffset: 150, 157 endOffset: 750 158 }); 159 160 test_animation_range({ 161 rangeStart: '150px', 162 rangeEnd: '75em', 163 startOffset: 150, 164 endOffset: 750 165 }); 166 167 test_animation_range({ 168 rangeStart: 'calc(1% + 135px)', 169 rangeEnd: 'calc(70em + 50px)', 170 startOffset: 150, 171 endOffset: 750 172 }); 173 174 // Test animation-range via timeline-scope. 175 test_animation_range({ 176 rangeStart: 'calc(1% + 135px)', 177 rangeEnd: 'calc(70em + 50px)', 178 startOffset: 150, 179 endOffset: 750 180 }, template_with_scope, '(scoped)'); 181 182 </script>