滚动穿透
问题描述
在移动端 WEB 开发的时候(小程序也雷同),如上录屏所示,如果页面超过一屏高度出现滚动条时,在 fixed 定位的弹窗遮罩层上进行滑动,它下面的内容也会跟着一起滚动,看起来好像事件穿透到下面的DOM元素上一样,我们姑且称之为滚动穿透。
问题原因
能够猜想是文档(document)的滚动事件被触发了,如果能禁用滚动事件就好办了。
案例伪代码
1 | <div class="btn">点击出现弹窗</div> |
1 | .popup-mask { |
✅ 解决方案A (touch-action)
默认情况下,平移(滚动)和缩放手势由浏览器专门处理,但是可以通过 CSS 特性 touch-action 来改变触摸手势的行为。摘取几个 touch-action 的值如下。
值 | 描述 |
---|---|
auto | 启用浏览器处理所有平移和缩放手势。 |
none | 禁用浏览器处理所有平移和缩放手势。 |
manipulation | 启用平移和缩放手势,但禁用其他非标准手势,例如双击缩放。 |
pinch-zoom | 启用页面的多指平移和缩放。 |
于是在 popup 元素上设置该属性,禁用元素(及其不可滚动的后代)上的所有手势就可以解决该问题了。
1 | .popup { |
Note: [无障碍设计] 阻止页面缩放可能会影响视力不佳的人阅读和理解页面内容,不过小程序本身好像就不可以缩放!
✅ 解决方案B (event.preventDefault)
来自 W3C 的一个标准。
大意是说,在 touchstart 和 touchmove 事件中调用 preventDefault 方法可以阻止任何关联事件的默认行为,包括鼠标事件和滚动。
因此我们可以这样处理。
Step 1、监听弹窗最外层元素(popup)的 touchmove 事件并阻止默认行为来禁用所有滚动(包括弹窗内部的滚动元素)。
Step 2、释放弹窗内的滚动元素,允许其滚动:同样监听 touchmove 事件,但是阻止该滚动元素的冒泡行为(stopPropagation),使得在滚动的时候最外层元素(popup)无法接收到 touchmove 事件。
1 | const popup = document.querySelector('.popup') |
滚动溢出
问题描述
如上录屏所示,弹窗内也含有滚动元素,在滚动元素滚到底部或顶部时,再往下或往上滚动,也会触发页面的滚动,这种现象称之为滚动链(scroll chaining), 但是感觉滚动溢出(overscroll)这个名字更言辞达意。
❌ 解决方案A (overscroll-behavior)
overscroll-behavior 是 CSS 的一个特性,允许控制浏览器滚动到边界的表现,它有如下几个值。
值 | 描述 |
---|---|
auto | 默认效果,元素的滚动可以传播到祖先元素。 |
contain | 阻止滚动链,滚动不会传播到祖先元素,但是会显示节点自身的局部效果。例如 Android 上过度滚动的发光效果或 iOS 上的橡皮筋效果。 |
none | 与 contain 相同,但是会阻止自身的过度效果。 |
所以可以这样解决问题:
1 | .content { |
简洁干净高性能,不过 Safari 全系不支持,兼容性如下,有没有感觉 Safari 就是现代版的 IE(偶然听路人说的)!
✅ 解决方案B (event.preventDefault)
借用 event.preventDefault 的能力,当组件滚动到底部或顶部时,通过调用 event.preventDefault 阻止所有滚动,从而页面滚动也不会触发了,而在滚动之间则不做处理。
1 | let initialPageY = 0 |
解决方案完整 Demo
https://github.com/Barrior/cases/blob/main/overscroll.html
作者:Barrior https://segmentfault.com/a/1190000040675446