滚动链接效果¶
滚动链接效果的定义是在网页上实现的一种效果,其中某些内容会根据滚动位置发生变化,例如更新定位属性以产生视差滚动效果。本文讨论了滚动链接效果、其对性能的影响、相关工具以及可能的缓解技术。
滚动效果说明¶
滚动效果通常通过监听 scroll
事件并以某种方式更新页面上的元素来实现(通常是 CSS 的 [position
]((https://mdn.org.cn/en-US/docs/Web/CSS/Position “position CSS 属性设置元素在文档中的定位方式。top、right、bottom 和 left 属性决定了定位元素的最终位置。”) 或 transform
属性。)你可以在 CSS Scroll API:用例 中找到一些此类效果的示例。
在浏览器同步地在浏览器主线程上进行滚动的浏览器中,这些效果运行良好。但是,现在大多数浏览器都支持某种异步滚动,以便为用户提供一致的每秒 60 帧的体验。在异步滚动模型中,视觉滚动位置在合成线程中更新,并在 scroll
事件在 DOM 中更新并在主线程上触发之前对用户可见。这意味着实现的效果将稍微滞后于用户看到的滚动位置。这可能导致效果滞后、卡顿或抖动——简而言之,这是我们想要避免的。
下面是几个在异步滚动中效果不佳的示例,以及可以正常工作的等效版本
示例 1:粘性定位¶
这是一个粘性定位效果的实现,其中“toolbar” div 在向下滚动时会粘贴到屏幕顶部。
<body style="height: 5000px" onscroll="document.getElementById('toolbar').style.top = Math.max(100, window.scrollY) + 'px'">
<div id="toolbar" style="position: absolute; top: 100px; width: 100px; height: 20px; background-color: green"></div>
</body>
这种粘性定位的实现依赖于滚动事件监听器来重新定位“toolbar” div。由于滚动事件监听器在浏览器主线程上的 JavaScript 中运行,因此相对于用户可见的滚动它是异步的。因此,在异步滚动中,事件处理程序将相对于用户可见的滚动延迟,因此 div 不会像预期的那样保持视觉固定。相反,它会随着用户的滚动移动,然后在滚动事件处理程序运行时“捕捉”回其位置。这种持续的移动和捕捉将导致抖动的视觉效果。一种无需滚动事件监听器即可实现此效果的方法是使用为此目的而设计的 CSS 属性
<body style="height: 5000px">
<div id="toolbar" style="position: sticky; top: 0px; margin-top: 100px; width: 100px; height: 20px; background-color: green"></div>
</body>
此版本在异步滚动中效果良好,因为“toolbar” div 的位置由浏览器在用户滚动时更新。
示例 2:滚动捕捉¶
下面是滚动捕捉的实现,其中当用户滚动停止在某个目标附近时,滚动位置会捕捉到该特定目标。
<body style="height: 5000px">
<script>
function snap(destination) {
if (Math.abs(destination - window.scrollY) < 3) {
scrollTo(window.scrollX, destination);
} else if (Math.abs(destination - window.scrollY) < 200) {
scrollTo(window.scrollX, window.scrollY + ((destination - window.scrollY) / 2));
setTimeout(snap, 20, destination);
}
}
var timeoutId = null;
addEventListener("scroll", function() {
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(snap, 200, parseInt(document.getElementById('snaptarget').style.top));
}, true);
</script>
<div id="snaptarget" class="snaptarget" style="position: relative; top: 200px; width: 100%; height: 200px; background-color: green"></div>
</body>
在此示例中,有一个滚动事件监听器检测滚动位置是否在“snaptarget” div 顶部 200 像素内。如果是,则触发一个动画将滚动位置“捕捉”到 div 的顶部。由于此动画由浏览器主线程上的 JavaScript 驱动,因此它可能会被其他选项卡或其他窗口中运行的其他 JavaScript 中断。因此,动画最终可能看起来很卡顿,而不是预期的那么流畅。相反,使用 CSS snap-points 属性将允许浏览器异步运行动画,从而为用户提供流畅的视觉效果。
<body style="height: 5000px">
<style>
body, /* blink currently has bug that requires declaration on `body` */
html {
scroll-snap-type: y proximity;
}
.snaptarget {
scroll-snap-align: start;
position: relative;
top: 200px;
height: 200px;
background-color: green;
}
</style>
<div class="snaptarget"></div>
</body>
即使浏览器主线程上有运行缓慢的 Javascript,此版本也可以在浏览器中流畅地工作。
其他效果¶
在许多情况下,可以使用 CSS 重新实现滚动链接效果,并使其在合成线程上运行。但是,在某些情况下,浏览器提供的当前 API 不允许这样做。但是,在所有情况下,如果 Firefox 检测到页面上存在滚动链接效果,它都会向开发者控制台显示警告(从版本 46 开始)。不使用 JavaScript 监听滚动事件来使用滚动效果的页面不会收到此警告。请参阅 Firefox 中的异步滚动 博客文章,了解一些可以使用 CSS 实现以避免卡顿的效果示例。
未来的改进¶
展望未来,我们希望在合成器中支持更多效果。为此,我们需要你(是的,就是你!)告诉我们更多关于你尝试实现的滚动链接效果类型的信息,以便我们能够找到在合成器中支持它们的好方法。目前有一些关于 API 的提案可以实现此类效果,并且它们都有各自的优缺点。目前正在考虑的提案包括
Web Animations:一个新的 API,用于精确控制 JavaScript 中的 Web 动画,以及一个 附加提案,将滚动位置映射到时间并将其用作动画的时间线。
CompositorWorker:允许 JavaScript 在合成线程上以小块形式运行,前提是它不会导致帧率下降。
滚动自定义:引入了一个新的 API,以便内容可以指示如何应用和使用滚动增量。截至撰写本文时,Mozilla 不打算支持此提案,但为了完整性而将其包含在内。
行动号召¶
如果你对以下方面有任何想法或意见
上述任何提案在滚动链接效果的上下文中。
你正在尝试实现的滚动链接效果。
任何其他相关问题或想法。
请与我们联系!你可以在 public-houdini 邮件列表中加入讨论。