html - Why does changing the height of a 'position:sticky' element alter the scroll position on Chrome but not Safari?
问题描述
I've noticed that Chrome and Safari handle position: sticky
on elements slightly differently.
Specifically, when a sticky element is made taller, and the window is currently scrolled such that the sticky element is offset from its initial position, Chrome will alter the scrollTop
position by the same amount - giving the appearance of the content staying still while the sticky element grows over the top.
In Safari, the scrollTop
position is unchanged in this scenario, giving the appearance of the content moving down to accommodate the sticky element's increased height.
I've created a code snippet below to demonstrate how the browser behaves in this situation. The screenshots above show how this demo behaves on each browser, but you can try it for yourself here.
function grow() {
const header = document.getElementById("header");
document.getElementById("header").classList.toggle("large-header");
updateScrollText();
}
function updateScrollText() {
const container = document.getElementById("container");
const scrollParent = getScrollParent(container);
document.getElementById("scrollbarpos1").innerHTML = scrollParent.scrollTop;
document.getElementById("scrollheight1").innerHTML = scrollParent.scrollHeight;
document.getElementById("containerheight1").innerHTML = container.offsetHeight;
document.getElementById("scrollbarpos2").innerHTML = scrollParent.scrollTop;
document.getElementById("scrollheight2").innerHTML = scrollParent.scrollHeight;
document.getElementById("containerheight2").innerHTML = container.offsetHeight;
}
function getScrollParent(node) {
if (node == null) {
return null;
}
if (node.scrollHeight > node.clientHeight) {
return node;
} else {
return getScrollParent(node.parentNode);
}
}
window.onscroll = updateScrollText;
window.onload = updateScrollText;
#header {
background-color: #CACACA;
position: sticky;
top: 0;
padding: 20px;
}
.large-header {
height: 100px;
}
.content {
background-color: #a2a6c4;
height: 1500px;
}
.shift-down {
margin-top: 50px;
}
<div id="container">
<div id="header">
<button type="button" onclick="grow()">Grow/Shrink</button>
</div>
<div class="content">
<br>
Scrollbar position: <span id="scrollbarpos1">0</span>
<br>
Scroll height: <span id="scrollheight1">0</span>
<br>
Container height: <span id="containerheight1">0</span>
<br>
<br>
Voluptatibus omnis perspiciatis consequatur magni error exercitationem saepe qui. Ipsa sint non labore voluptates. Asperiores aut non ullam aut sit omnis ducimus in. Aut enim nihil unde ad expedita. Ratione necessitatibus quasi dolorem sunt aperiam nobis ducimus.
Sequi quasi maiores eos aut non. Ipsam delectus sit facilis aut. Dolor facilis eum dignissimos. Vero reiciendis odio quis blanditiis.
Error nesciunt rem facilis. Neque labore et qui sequi eos corrupti dolorem. Reprehenderit qui voluptatem et neque ducimus ipsum similique fugit. Ea sint alias qui laborum nesciunt. Nihil ex repellendus odit sint unde fuga.
A eum nulla ut cumque necessitatibus culpa exercitationem unde. Corrupti sit minima eveniet et aut possimus sapiente. Est accusantium aut ut numquam illo.
Praesentium fugit pariatur eum ad velit distinctio culpa id. Quia voluptatum dignissimos consequatur. Eaque nihil voluptas in voluptas voluptas eius voluptas.
<br>
<br>
<div class="shift-down">
Scrollbar position: <span id="scrollbarpos2">0</span>
<br>
Scroll height: <span id="scrollheight2">0</span>
<br>
Container height: <span id="containerheight2">0</span>
</div>
</div>
</div>
I looked at the W3C spec on Positioned Layout but couldn't find anything specifically defining how this is supposed to work.
So my questions are:
- Why is this behaviour different on these two browsers?
- Which one, if any, is "correct"?
- Is there any way to make both browsers behave identically (either way)?
解决方案
Why is this behaviour different on these two browsers?
To understand this, you first need to understand how a sticky element takes up space on a page. As you scroll past the sticky element, the rendered part of the sticky element follows the top of your viewport, but the element still physically takes up space where it was originally placed:
This puts the browser in a bit of a weird situation when you resize the header, because it's actually an element that you have already scrolled past. There are two approaches to solving this. You can do like Safari and Firefox and always keep the same distance to the top of the document, or you can do like Chromium and move the viewport so that it follows the element you are currently looking at:
I believe Safari and Firefox do it their way, because it's the easiest solution, and that's just how every browser has always done it. I believe Chromium has changed it's approach recently because it has a distinct advantage when a user is reading articles with slow loading ads that change size after they've loaded:
As you can see above, when the ad loads in Safari and Firefox, it causes a major shift in the page content, which annoys and disorients the user. This can especially be bad if there are several ads that load shortly after one another. With the Chromium approach, the viewport is adjusted so that the user doesn't even notice that anything has happened.
I was not able to find any spec or discussion to back up my claims, but i'm pretty sure that this is the reasoning behind. I am not sure if Firefox or Safari has had a reason to not implement Chromiums approach, or if they just didn't feel it was that important.
Which one, if any, is "correct"?
Which solution is best is probably a very complicated and somewhat subjective discussion that i won't go into, although the benefits of Chromiums approach is undeniable.
Is there any way to make both browsers behave identically (either way)?
When you press the Grow/Shrink
button, you can always detect if the scroll height changes, and then set it back right after it was changed. You can also do the opposite and change the scroll height yourself so all browsers behave like Chromium. Here is an old article from someone who did exactly that:
https://simonerescio.it/en/2017/03/chrome-changes-its-center-of-gravity-reference-is-not-the-document-but-the-viewport
推荐阅读
- nlp - 微调 Huggingface 的输入/输出格式 RobertaForQuestionAnswering
- python - 如何对远程计算机上的 Errno 13 Permission Denied 进行故障排除?
- powerbi - 从 Power BI 桌面/查询中的单元格中提取短语
- python - 基于递归的python排列算法
- python - Pandas 将时间间隔与时间序列重叠
- c++ - 如何链接到其他代码实现?
- python - 将字典的字符串表示形式转换为字典
- postgresql - 请在首选项对话框中配置 PostgreSQL 二进制路径
- flutter - Flutter 全局变量
- openiddict - 某些客户端的 Openiddict 回调错误 - 不是全部