device-pixel-adjustment.html (2869B)
1 <!DOCTYPE html> 2 <link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <style> 6 7 body { 8 height: 200vh; 9 } 10 #anchor { 11 width: 100px; 12 height: 100px; 13 background-color: blue; 14 } 15 16 </style> 17 <div id="expander"></div> 18 <div id="anchor"></div> 19 <script> 20 21 // This tests that scroll anchor adjustments can happen by quantities smaller 22 // than a device pixel. 23 // 24 // Unfortunately, we can't test this by simply reading 'scrollTop', because 25 // 'scrollTop' may be rounded to the nearest CSS pixel. So, to test that 26 // subpixel adjustments can in fact happen, we repeatedly trigger a scroll 27 // adjustment in a way that would produce a different final .scrollTop value, 28 // depending on whether or not we rounded each adjustment as we apply it. 29 30 test(() => { 31 let scroller = document.scrollingElement; 32 let expander = document.querySelector("#expander"); 33 let anchor = document.querySelector("#anchor"); 34 const initialTop = 10; 35 36 // Scroll 10px to activate scroll anchoring 37 scroller.scrollTop = initialTop; 38 39 // Helper to insert a div with specified height before the anchor node 40 function addChild(height) { 41 let child = document.createElement("div"); 42 child.style.height = `${height}px`; 43 anchor.before(child); 44 } 45 46 // Calculate what fraction of a CSS pixel corresponds to one device pixel 47 let devicePixel = 1.0 / window.devicePixelRatio; 48 assert_true(devicePixel <= 1.0, "there should be more device pixels than CSS pixels"); 49 50 // The 0.5 is an arbitrary scale when creating the subpixel delta 51 let delta = 0.5 * devicePixel; 52 53 // To help us check for for premature rounding of adjustments, we'll 54 // trigger "count" subpixel adjustments of size "delta", where "count" is 55 // the first positive integer such that: 56 // round(count * delta) != count * round(delta) 57 // As round(X) and count are integers, this happens when: 58 // count * delta = count * round(delta) +/- 1 59 // Solving for count: 60 // count = 1 / abs(delta - round(delta)) 61 // Note that we don't need to worry about the denominator being zero, as: 62 // 0 < devicePixel <= 1 63 // And so halving devicePixel should never yield a whole number. 64 let count = 1 / Math.abs(delta - Math.round(delta)); 65 66 for (let i = 0; i < count; i++) { 67 addChild(delta); 68 // Trigger an anchor adjustment by forcing a layout flush 69 scroller.scrollTop; 70 } 71 72 let destination = Math.round(initialTop + delta * count); 73 assert_equals(scroller.scrollTop, destination, 74 `adjusting by ${delta}px, ${count} times, should be the same as adjusting by ${delta * count}px, once.`); 75 }, "Test that scroll anchor adjustments can happen by a sub device-pixel amount."); 76 77 </script>