scrollIntoView-fixed.html (8988B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>CSSOM View - scrollIntoView from position:fixed</title> 5 <meta charset="utf-8"> 6 <meta name="viewport" content="width=device-width,minimum-scale=1"> 7 <link rel="author" title="David Bokan" href="mailto:bokan@chromium.org"> 8 <link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview"> 9 <script src="/resources/testharness.js"></script> 10 <script src="/resources/testharnessreport.js"></script> 11 <style> 12 body { 13 width: 1000vw; 14 height: 1000vh; 15 /* stripes so we can see scroll offset more easily */ 16 background: repeating-linear-gradient( 17 45deg, 18 #A2CFD9, 19 #A2CFD9 100px, 20 #C3F3FF 100px, 21 #C3F3FF 200px 22 ); 23 } 24 25 .fixedContainer { 26 position: fixed; 27 bottom: 10px; 28 left: 10px; 29 width: 150px; 30 height: 150px; 31 background-color: coral; 32 } 33 34 .fixedContainer.scrollable { 35 overflow: auto; 36 left: unset; 37 right: 10px; 38 } 39 40 button { 41 position: absolute; 42 margin: 5px; 43 } 44 45 .target { 46 position: absolute; 47 width: 10px; 48 height: 10px; 49 background-color: blue; 50 left: 50%; 51 top: 50%; 52 } 53 54 .scrollable .target { 55 left: 200%; 56 top: 200%; 57 } 58 59 iframe { 60 width: 96vw; 61 height: 300px; 62 position: absolute; 63 left: 2vw; 64 top: 100px; 65 } 66 </style> 67 <script> 68 </script> 69 </head> 70 <body> 71 <div style="width:90vw"> 72 <p> 73 The orange boxes are position: fixed. Clicking ScrollIntoView in each box 74 will attempt to scroll into view the blue target element inside that fixed 75 container to block/inline: start (i.e. aligned with top left corner in RTL). 76 </p> 77 <p> 78 scrollIntoView from a position:fixed element must not scroll its 79 containing frame; however, it must scroll further ancestor scrollers as 80 the element isn't fixed in relation to them. 81 </p> 82 </div> 83 <iframe></iframe> 84 <div class="fixedContainer"> 85 Box A 86 <button id="fixedContainerBtn">ScrollIntoView</button> 87 <div class="target"></div> 88 </div> 89 <div class="fixedContainer scrollable"> 90 Box C 91 <button id="scrollableFixedContainerBtn">ScrollIntoView</button> 92 <div class="target"></div> 93 </div> 94 <script> 95 if (typeof setup != 'undefined') { 96 setup({ explicit_done: true }); 97 window.addEventListener("load", runTests); 98 } 99 100 function scrollIntoView(evt) { 101 const container = evt.target.closest('.fixedContainer'); 102 const target = container.querySelector('.target'); 103 target.scrollIntoView({block: 'start', inline: 'start'}); 104 } 105 106 document.querySelectorAll('button').forEach((btn) => { 107 btn.addEventListener('click', scrollIntoView); 108 }); 109 110 const iframe = document.querySelector('iframe'); 111 iframe.onload = () => { 112 frames[0].document.querySelectorAll('button').forEach((btn) => { 113 btn.addEventListener('click', scrollIntoView); 114 }); 115 } 116 iframe.srcdoc = ` 117 <!DOCTYPE html> 118 <style> 119 body { 120 height: 200vh; 121 width: 200vw; 122 /* stripes so we can see scroll offset more easily */ 123 background: repeating-linear-gradient( 124 45deg, 125 #C3A2D9, 126 #C3A2D9 50px, 127 #E5C0FF 50px, 128 #E5C0FF 100px 129 ); 130 } 131 .fixedContainer { 132 position: fixed; 133 bottom: 10px; 134 left: 10px; 135 width: 150px; 136 height: 150px; 137 background-color: coral; 138 } 139 140 .fixedContainer.scrollable { 141 overflow: auto; 142 left: unset; 143 right: 10px; 144 } 145 146 button { 147 position: absolute; 148 margin: 5px; 149 } 150 151 .target { 152 position: absolute; 153 width: 10px; 154 height: 10px; 155 background-color: blue; 156 left: 50%; 157 top: 50%; 158 } 159 160 .scrollable .target { 161 left: 200%; 162 top: 200%; 163 } 164 </style> 165 IFRAME 166 <div class="fixedContainer"> 167 Box B 168 <button id="fixedContainerBtn">ScrollIntoView</button> 169 <div class="target"></div> 170 </div> 171 <div class="fixedContainer scrollable"> 172 Box D 173 <button id="scrollableFixedContainerBtn">ScrollIntoView</button> 174 <div class="target"></div> 175 </div> 176 `; 177 178 function reset() { 179 [document, frames[0].document].forEach((doc) => { 180 doc.scrollingElement.scrollLeft = 0; 181 doc.scrollingElement.scrollTop = 0; 182 doc.querySelectorAll('.fixedContainer').forEach((e) => { 183 e.scrollLeft = 0; 184 e.scrollTop = 0; 185 }); 186 }); 187 } 188 189 function runTests() { 190 // Test scrollIntoView from a plain, unscrollable position:fixed element. 191 // Nothing should scroll. 192 test(() => { 193 reset() 194 const container = document.querySelector('.fixedContainer:not(.scrollable)'); 195 const target = container.querySelector('.target'); 196 target.scrollIntoView({block: 'start', inline: 'start'}); 197 assert_equals(window.scrollX, 0, 'must not scroll window [scrollX]'); 198 assert_equals(window.scrollY, 0, 'must not scroll window [scrollY]'); 199 }, `[Box A] scrollIntoView from unscrollable position:fixed`); 200 201 // Same as above but from inside an iframe. Since the container is fixed 202 // only to the iframe, we should scroll the outer window. 203 test(() => { 204 reset() 205 const container = frames[0].document.querySelector('.fixedContainer:not(.scrollable)'); 206 const target = container.querySelector('.target'); 207 target.scrollIntoView({block: 'start', inline: 'start'}); 208 209 // Large approx to account for differences like scrollbars 210 assert_approx_equals(window.scrollX, 100, 20, 'must scroll outer window [scrollX]'); 211 assert_approx_equals(window.scrollY, 300, 20, 'must scroll outer window [scrollY]'); 212 assert_equals(frames[0].window.scrollX, 0, 'must not scroll iframe [scrollX]'); 213 assert_equals(frames[0].window.scrollY, 0, 'must not scroll iframe [scrollY]'); 214 }, `[Box B] scrollIntoView from unscrollable position:fixed in iframe`); 215 216 // Test scrollIntoView from a scroller that's position fixed. The 217 // scroller should be scrolled but shouldn't bubble the scroll as the 218 // window scrolling won't change the target's position. 219 test(() => { 220 reset() 221 const container = document.querySelector('.fixedContainer.scrollable'); 222 const target = container.querySelector('.target'); 223 target.scrollIntoView({block: 'start', inline: 'start'}); 224 // Large approx to account for differences like scrollbars 225 assert_equals(window.scrollX, 0, 'must not scroll window [scrollX]'); 226 assert_equals(window.scrollY, 0, 'must not scroll window [scrollY]'); 227 assert_approx_equals(container.scrollLeft, 145, 20, 228 'scrollIntoView in container [scrollLeft]'); 229 assert_approx_equals(container.scrollTop, 145, 20, 230 'scrollIntoView in container [scrollTop]'); 231 }, `[Box C] scrollIntoView from scrollable position:fixed`); 232 233 // Same as above but from inside an iframe. In this case, the scroller 234 // should bubble the scroll but skip its own frame (as it's fixed with 235 // respect to its own frame's scroll offset) and bubble to the outer 236 // window. 237 test(() => { 238 reset() 239 const container = frames[0].document.querySelector('.fixedContainer.scrollable'); 240 const target = container.querySelector('.target'); 241 target.scrollIntoView({block: 'start', inline: 'start'}); 242 // Large approx to account for differences like scrollbars 243 assert_approx_equals(window.scrollX, 740, 20, 'must scroll outer window [scrollX]'); 244 assert_approx_equals(window.scrollY, 360, 20, 'must scroll outer window [scrollY]'); 245 assert_approx_equals(container.scrollLeft, 145, 20, 246 'scrollIntoView in container [scrollLeft]'); 247 assert_approx_equals(container.scrollTop, 145, 20, 248 'scrollIntoView in container [scrollTop]'); 249 assert_equals(frames[0].window.scrollX, 0, 'must not scroll iframe [scrollX]'); 250 assert_equals(frames[0].window.scrollY, 0, 'must not scroll iframe [scrollY]'); 251 }, `[Box D] scrollIntoView from scrollable position:fixed in iframe`); 252 253 done(); 254 } 255 256 </script> 257 </body> 258 </html>