原文 Pin Scrolling to Bottom

网站最令人讨厌的五件事中,也许就有这件事。你正试图阅读某些内容(或点击某些内容!),页面却突然从你的下方移动了(或者你点击错了!)。有一种 CSS 属性可以帮助解决这个问题。此外,我们还可以利用它来做一些以前只属于 JavaScript 领域的事情。

CSS 中的 overflow-anchor 属性相对较新,它于 2017 年首次在 Chrome 浏览器中出现,2019 年在 Firefox 中出现,现在 Edge 在 2020 年 Chrome 浏览器过渡时也采用了该属性。幸运的是,它的使用在很大程度上是一种增强。我们的想法是,浏览器在默认情况下尽量允许位置移动。如果你不喜欢浏览器的处理方式,可以使用 overflow-ancher 来关闭它。因此,一般情况下,你永远不会碰它。

不过,正如你所猜想的那样,我们可以利用这个小妙招来玩点 CSS 小把戏。我们可以强制滚动元素保持在底部,即使我们添加了新内容。

Chat is a classic example of pin-to-bottom scrolling.

在 Slack 这样的用户界面中,如果我们向下滚动到一个频道中最近的消息,当有新消息到达时,我们就能立即在底部看到这些消息,而无需手动重新向下滚动才能看到。

Hay! 本专题由 Ryan Hunt 制作,Nicolas Chevobbe 也参与了制作。

正如 Ryan 所说:你有没有试过在添加新内容时实施可滚动元素,并将用户固定在底部?要做到这一点并非易事。

至少,您需要使用 JavaScript 检测新内容的添加,并强制滚动元素到底部。下面是一种使用 JavaScript 中的 MutationObserver 来检测新内容并强制滚动的技术:

const scrollingElement = document.getElementById("scroller");
 
const config = { childList: true };
 
const callback = function (mutationsList, observer) {
  for (let mutation of mutationsList) {
    if (mutation.type === "childList") {
      window.scrollTo(0, document.body.scrollHeight);
    }
  }
};
 
const observer = new MutationObserver(callback);
observer.observe(scrollingElement, config);

这是在线示例

但我觉得只用 CSS 的解决方案更有吸引力!上面的版本有一些用户体验方面的缺陷,我们稍后会介绍。

这里的诀窍在于,浏览器默认情况下已经做了滚动锚点。但浏览器要尽可能的不移动页面。因此,当添加的新内容可能会移动页面的视觉位置时,浏览器会尽量避免这种情况发生。在这种不寻常的情况下,我们想要的恰恰相反。我们希望页面停留在页面的底部,而页面在视觉上移动,因为它被迫停留在底部。

以下是该技巧的工作原理。首先,在滚动父元素中加入一些 HTML 代码:

<div id="scroller">
  <!-- new content dynamically inserted here -->
  <div id="anchor"></div>
</div>

所有元素自然都带有 overflow-anchor: auto;,这意味着当它们出现在屏幕上时,它们会试图阻止页面移动,但我们可以使用 overflow-anchor: none; 将其关闭。因此,诀窍就是针对所有动态插入的内容,将其关闭:

#scroller * {
  overflow-anchor: none;
}

然后只强制该锚元素使用滚动锚定,其他元素一律不用:

#anchor {
  overflow-anchor: auto;
  height: 1px;
}

现在,一旦该锚点在页面上可见,浏览器就会强制将滚动位置固定在它上面,由于它是滚动元素中的最后一个内容,因此会被固定在底部。

在线示例

这里有两个小小的注意事项…

  1. 注意锚点必须有一定的大小。我们在这里使用高度来确保它不是一个没有尺寸的 折叠/空 元素,否则将无法工作。
  2. 为了使滚动锚点生效,页面必须至少滚动一次。

第二个问题比较棘手。一种方法是根本不处理它,只等用户滚动到底部,效果就会从那里开始起作用。这其实也不错,因为如果用户从底部滚动,效果就会停止,而这正是你想要的。在上面的 JavaScript 版本中,请注意即使你试图向上滚动,它也会强制你滚动到底部,这就是 Ryan 所说的 “不容易正确完成 “的意思。

如果需要立即启动效果,诀窍就是让滚动元素可以立即滚动:

body {
  height: 100.001vh;
}

然后立即触发一个非常轻微的滚动:

document.scrollingElement.scroll(0, 1);

这样就可以了。在上面的演示中,可以取消这些行的注释并进行尝试。