要点
借助 CSS overscroll-behavior
属性,开发者可以在到达内容顶部/底部时替换浏览器的默认溢出滚动行为。用例包括在移动设备上停用“拉动即可刷新”功能、移除滚动超出范围时的光晕和橡皮筋效果,以及防止页面内容在模态窗口/叠加层下方滚动。
背景
滚动边界和滚动链接
滚动是与网页互动最基本的方式之一,但由于浏览器的默认行为古怪,因此某些用户体验模式可能很难处理。例如,假设有一个包含大量用户可能需要滚动浏览的项的抽屉式应用列表。当用户滚动到底部时,由于没有更多内容可供使用,因此溢出容器会停止滚动。换句话说,用户到达了“滚动边界”。但请注意,如果用户继续滚动,会发生什么情况。抽屉式导航栏后面的内容开始滚动!滚动将由父容器(在示例中为主页面本身)接管。
事实证明,这种行为称为滚动链接,是浏览器滚动内容时的默认行为。默认值通常非常不错,但有时并不理想,甚至出乎意料。某些应用可能希望在用户到达滚动边界时提供不同的用户体验。
拉动刷新效果
拉动刷新是一种由 Facebook 和 Twitter 等移动应用普及的直观手势。向下拉动社交媒体动态并松开手指,系统会创建新的空间来加载较新的帖子。事实上,这种特殊的用户体验已经广受欢迎,Android 版 Chrome 等移动浏览器也采用了相同的效果。在页面顶部向下滑动可刷新整个页面:
对于 Twitter PWA 等情况,停用原生下拉刷新操作可能很有用。为什么呢?在此应用中,您可能不希望用户意外刷新页面。您还可能会看到双重刷新动画!或者,您也可以自定义浏览器的操作,使其更贴近网站的品牌形象。遗憾的是,这种类型的自定义很难实现。开发者最终会编写不必要的 JavaScript、添加非无效触摸监听器(会阻止滚动),或将整个网页粘贴到 100vw/vh <div>
中(以防止网页溢出)。这些权宜解决方法对滚动性能有已记录的负面影响。
我们可以做得更好!
隆重推出 overscroll-behavior
overscroll-behavior
属性 是一项新的 CSS 功能,用于控制在您超出滚动容器(包括网页本身)时会发生的行为。您可以使用它取消滚动链接、停用/自定义“拉动以刷新”操作、在 iOS 上停用橡皮筋效果(当 Safari 实现 overscroll-behavior
时),等等。最棒的是,使用 overscroll-behavior
不会像前言中提到的黑客攻击那样对网页性能产生不利影响!
该属性有以下三个可能的值:
- auto - 默认。从元素发起的滚动操作可能会传播到祖先元素。
- contain - 可防止滚动链接。滚动不会传播到祖先,但会显示节点内的局部效果。例如,Android 上的滚动超出发光效果,或 iOS 上的橡皮筋效果,可在用户到达滚动边界时通知用户。注意:在
html
元素上使用overscroll-behavior: contain
可防止发生滚动回弹导航操作。 - none - 与
contain
相同,但它还会阻止节点本身中的滚动回弹效果(例如 Android 滚动回弹发光或 iOS 橡皮筋效果)。
我们来通过一些示例来了解如何使用 overscroll-behavior
。
防止滚动超出固定位置元素
聊天框场景
假设有一个固定位置的聊天框,位于页面底部。目的是让聊天框成为一个独立的组件,并与其背后的内容分开滚动。不过,由于滚动链接,文档会在用户点击聊天记录中的最后一条消息后立即开始滚动。
对于此应用,更适合让从聊天框中开始滚动的内容保持在聊天内。为此,我们可以向包含聊天消息的元素添加 overscroll-behavior: contain
:
#chat .msgs {
overflow: auto;
overscroll-behavior: contain;
height: 300px;
}
本质上,我们是在聊天框的滚动上下文和主页面之间创建逻辑分隔。最终结果是,当用户到达聊天记录的顶部/底部时,主页面会保持不变。在聊天框中开始的滚动操作不会传播出去。
网页叠加场景
“滚动到底部”场景的另一种变体是,您会看到内容在固定位置叠加层后面滚动。overscroll-behavior
是绝佳的赠品!浏览器是为了提供帮助,但最终却让网站看起来存在问题。
示例 - 包含和不包含 overscroll-behavior: contain
的模态窗口:
停用下拉刷新
只需一行 CSS 代码即可关闭“拉动即可刷新”操作。只需阻止整个视口定义元素上的滚动链接即可。在大多数情况下,该值为 <html>
或 <body>
:
body {
/* Disables pull-to-refresh but allows overscroll glow effects. */
overscroll-behavior-y: contain;
}
通过这项简单的添加,我们修复了聊天框演示中的双重下拉刷新动画,并改为实现使用更简洁的加载动画的自定义效果。收件箱刷新时,整个收件箱也会模糊处理:
以下是完整代码的代码段:
<style>
body.refreshing #inbox {
filter: blur(1px);
touch-action: none; /* prevent scrolling */
}
body.refreshing .refresher {
transform: translate3d(0,150%,0) scale(1);
z-index: 1;
}
.refresher {
--refresh-width: 55px;
pointer-events: none;
width: var(--refresh-width);
height: var(--refresh-width);
border-radius: 50%;
position: absolute;
transition: all 300ms cubic-bezier(0,0,0.2,1);
will-change: transform, opacity;
...
}
</style>
<div class="refresher">
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
</div>
<section id="inbox"><!-- msgs --></section>
<script>
let _startY;
const inbox = document.querySelector('#inbox');
inbox.addEventListener('touchstart', e => {
_startY = e.touches[0].pageY;
}, {passive: true});
inbox.addEventListener('touchmove', e => {
const y = e.touches[0].pageY;
// Activate custom pull-to-refresh effects when at the top of the container
// and user is scrolling up.
if (document.scrollingElement.scrollTop === 0 && y > _startY &&
!document.body.classList.contains('refreshing')) {
// refresh inbox.
}
}, {passive: true});
</script>
停用滚动回弹和橡皮筋效果
如需在到达滚动边界时停用弹跳效果,请使用 overscroll-behavior-y: none
:
body {
/* Disables pull-to-refresh and overscroll glow effect.
Still keeps swipe navigations. */
overscroll-behavior-y: none;
}
完整演示
将所有这些内容整合起来,完整的聊天框演示版使用 overscroll-behavior
创建自定义的“拉动刷新”动画,并禁止滚动超出聊天框 widget。这样可以提供最佳用户体验,如果没有 CSS overscroll-behavior
,实现起来会很棘手。