<!DOCTYPE html>
<html>
<head>
<title>CSSOM View - scrollIntoView from position:fixed</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1">
<link rel="author" title="David Bokan" href="mailto:[email protected]">
<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
body {
width: 1000vw;
height: 1000vh;
/* stripes so we can see scroll offset more easily */
background: repeating-linear-gradient(
45deg,
#A2CFD9,
#A2CFD9 100px,
#C3F3FF 100px,
#C3F3FF 200px
);
}
.fixedContainer {
position: fixed;
bottom: 10px;
left: 10px;
width: 150px;
height: 150px;
background-color: coral;
}
.fixedContainer.scrollable {
overflow: auto;
left: unset;
right: 10px;
}
button {
position: absolute;
margin: 5px;
}
.target {
position: absolute;
width: 10px;
height: 10px;
background-color: blue;
left: 50%;
top: 50%;
}
.scrollable .target {
left: 200%;
top: 200%;
}
iframe {
width: 96vw;
height: 300px;
position: absolute;
left: 2vw;
top: 100px;
}
</style>
<script>
</script>
</head>
<body>
<div style="width:90vw">
<p>
The orange boxes are position: fixed. Clicking ScrollIntoView in each box
will attempt to scroll into view the blue target element inside that fixed
container to block/inline: start (i.e. aligned with top left corner in RTL).
</p>
<p>
scrollIntoView from a position:fixed element must not scroll its
containing frame; however, it must scroll further ancestor scrollers as
the element isn't fixed in relation to them.
</p>
</div>
<iframe></iframe>
<div class="fixedContainer">
Box A
<button id="fixedContainerBtn">ScrollIntoView</button>
<div class="target"></div>
</div>
<div class="fixedContainer scrollable">
Box C
<button id="scrollableFixedContainerBtn">ScrollIntoView</button>
<div class="target"></div>
</div>
<script>
if (typeof setup != 'undefined') {
setup({ explicit_done: true });
window.addEventListener("load", runTests);
}
function scrollIntoView(evt) {
const container = evt.target.closest('.fixedContainer');
const target = container.querySelector('.target');
target.scrollIntoView({block: 'start', inline: 'start'});
}
document.querySelectorAll('button').forEach((btn) => {
btn.addEventListener('click', scrollIntoView);
});
const iframe = document.querySelector('iframe');
iframe.onload = () => {
frames[0].document.querySelectorAll('button').forEach((btn) => {
btn.addEventListener('click', scrollIntoView);
});
}
iframe.srcdoc = `
<!DOCTYPE html>
<style>
body {
height: 200vh;
width: 200vw;
/* stripes so we can see scroll offset more easily */
background: repeating-linear-gradient(
45deg,
#C3A2D9,
#C3A2D9 50px,
#E5C0FF 50px,
#E5C0FF 100px
);
}
.fixedContainer {
position: fixed;
bottom: 10px;
left: 10px;
width: 150px;
height: 150px;
background-color: coral;
}
.fixedContainer.scrollable {
overflow: auto;
left: unset;
right: 10px;
}
button {
position: absolute;
margin: 5px;
}
.target {
position: absolute;
width: 10px;
height: 10px;
background-color: blue;
left: 50%;
top: 50%;
}
.scrollable .target {
left: 200%;
top: 200%;
}
</style>
IFRAME
<div class="fixedContainer">
Box B
<button id="fixedContainerBtn">ScrollIntoView</button>
<div class="target"></div>
</div>
<div class="fixedContainer scrollable">
Box D
<button id="scrollableFixedContainerBtn">ScrollIntoView</button>
<div class="target"></div>
</div>
`;
function reset() {
[document, frames[0].document].forEach((doc) => {
doc.scrollingElement.scrollLeft = 0;
doc.scrollingElement.scrollTop = 0;
doc.querySelectorAll('.fixedContainer').forEach((e) => {
e.scrollLeft = 0;
e.scrollTop = 0;
});
});
}
function runTests() {
// Test scrollIntoView from a plain, unscrollable position:fixed element.
// Nothing should scroll.
test(() => {
reset()
const container = document.querySelector('.fixedContainer:not(.scrollable)');
const target = container.querySelector('.target');
target.scrollIntoView({block: 'start', inline: 'start'});
assert_equals(window.scrollX, 0, 'must not scroll window [scrollX]');
assert_equals(window.scrollY, 0, 'must not scroll window [scrollY]');
}, `[Box A] scrollIntoView from unscrollable position:fixed`);
// Same as above but from inside an iframe. Since the container is fixed
// only to the iframe, we should scroll the outer window.
test(() => {
reset()
const container = frames[0].document.querySelector('.fixedContainer:not(.scrollable)');
const target = container.querySelector('.target');
target.scrollIntoView({block: 'start', inline: 'start'});
// Large approx to account for differences like scrollbars
assert_approx_equals(window.scrollX, 100, 20, 'must scroll outer window [scrollX]');
assert_approx_equals(window.scrollY, 300, 20, 'must scroll outer window [scrollY]');
assert_equals(frames[0].window.scrollX, 0, 'must not scroll iframe [scrollX]');
assert_equals(frames[0].window.scrollY, 0, 'must not scroll iframe [scrollY]');
}, `[Box B] scrollIntoView from unscrollable position:fixed in iframe`);
// Test scrollIntoView from a scroller that's position fixed. The
// scroller should be scrolled but shouldn't bubble the scroll as the
// window scrolling won't change the target's position.
test(() => {
reset()
const container = document.querySelector('.fixedContainer.scrollable');
const target = container.querySelector('.target');
target.scrollIntoView({block: 'start', inline: 'start'});
// Large approx to account for differences like scrollbars
assert_equals(window.scrollX, 0, 'must not scroll window [scrollX]');
assert_equals(window.scrollY, 0, 'must not scroll window [scrollY]');
assert_approx_equals(container.scrollLeft, 145, 20,
'scrollIntoView in container [scrollLeft]');
assert_approx_equals(container.scrollTop, 145, 20,
'scrollIntoView in container [scrollTop]');
}, `[Box C] scrollIntoView from scrollable position:fixed`);
// Same as above but from inside an iframe. In this case, the scroller
// should bubble the scroll but skip its own frame (as it's fixed with
// respect to its own frame's scroll offset) and bubble to the outer
// window.
test(() => {
reset()
const container = frames[0].document.querySelector('.fixedContainer.scrollable');
const target = container.querySelector('.target');
target.scrollIntoView({block: 'start', inline: 'start'});
// Large approx to account for differences like scrollbars
assert_approx_equals(window.scrollX, 740, 20, 'must scroll outer window [scrollX]');
assert_approx_equals(window.scrollY, 360, 20, 'must scroll outer window [scrollY]');
assert_approx_equals(container.scrollLeft, 145, 20,
'scrollIntoView in container [scrollLeft]');
assert_approx_equals(container.scrollTop, 145, 20,
'scrollIntoView in container [scrollTop]');
assert_equals(frames[0].window.scrollX, 0, 'must not scroll iframe [scrollX]');
assert_equals(frames[0].window.scrollY, 0, 'must not scroll iframe [scrollY]');
}, `[Box D] scrollIntoView from scrollable position:fixed in iframe`);
done();
}
</script>
</body>
</html>