滚动穿透
问题形容
在挪动端 WEB 开发的时候(小程序也雷同),如上录屏所示,如果页面超过一屏高度呈现滚动条时,在 fixed 定位的弹窗遮罩层上进行滑动,它上面的内容也会跟着一起滚动,看起来如同事件穿透到上面的 DOM 元素上一样,咱们权且称之为滚动穿透。
问题起因
可能猜测是文档(document)的滚动事件被触发了,如果能禁用滚动事件就好办了。
案例伪代码
<div class="btn"> 点击呈现弹窗 </div>
<div class="popup">
<div class="popup-mask"></div>
<div class="popup-body popup-bottom">
<div class="header"> 我是题目 </div>
<div class="content">
<div>0</div>
<div>1</div>
<div>...</div>
</div>
</div>
</div>
.popup-mask {background-color: rgba(0, 0, 0, 0.5);
position: fixed;
z-index: 998;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.popup-body {
padding: 0 50px 40px;
background-color: #fff;
position: fixed;
z-index: 999;
}
✅ 解决方案 A (touch-action)
默认状况下,平移(滚动)和缩放手势由浏览器专门解决,然而能够通过 CSS 个性 touch-action 来扭转触摸手势的行为。摘取几个 touch-action 的值如下。
值 | 形容 |
---|---|
auto | 启用浏览器解决所有平移和缩放手势。 |
none | 禁用浏览器解决所有平移和缩放手势。 |
manipulation | 启用平移和缩放手势,但禁用其余非标准手势,例如双击缩放。 |
pinch-zoom | 启用页面的多指平移和缩放。 |
于是在 popup 元素上设置该属性,禁用元素(及其不可滚动的后辈)上的所有手势就能够解决该问题了。
.popup {touch-action: none;}
Note: [无障碍设计] 阻止页面缩放可能会影响视力不佳的人浏览和了解页面内容,不过小程序自身如同就不能够缩放!
✅ 解决方案 B (event.preventDefault)
来自 W3C 的一个规范。
粗心是说,在 touchstart 和 touchmove 事件中调用 preventDefault 办法能够阻止任何关联事件的默认行为,包含鼠标事件和滚动。
因而咱们能够这样解决。
Step 1、监听弹窗最外层元素(popup)的 touchmove 事件并阻止默认行为来禁用所有滚动(包含弹窗外部的滚动元素)。
Step 2、开释弹窗内的滚动元素,容许其滚动:同样监听 touchmove 事件,然而阻止该滚动元素的冒泡行为(stopPropagation),使得在滚动的时候最外层元素(popup)无奈接管到 touchmove 事件。
const popup = document.querySelector('.popup')
const scrollBox = document.querySelector('.content')
popup.addEventListener('touchmove', (e) => {
// Step 1: 阻止默认事件
e.preventDefault()})
scrollBox.addEventListener('touchmove', (e) => {
// Step 2: 阻止冒泡
e.stopPropagation()})
滚动溢出
问题形容
如上录屏所示,弹窗内也含有滚动元素,在滚动元素滚到底部或顶部时,再往下或往上滚动,也会触发页面的滚动,这种景象称之为滚动链(scroll chaining), 然而感觉滚动溢出(overscroll)这个名字更言辞达意。
❌ 解决方案 A (overscroll-behavior)
overscroll-behavior 是 CSS 的一个个性,容许管制浏览器滚动到边界的体现,它有如下几个值。
值 | 形容 |
---|---|
auto | 默认成果,元素的滚动能够流传到先人元素。 |
contain | 阻止滚动链,滚动不会流传到先人元素,然而会显示节点本身的部分成果。例如 Android 上适度滚动的发光成果或 iOS 上的橡皮筋成果。 |
none | 与 contain 雷同,然而会阻止本身的适度成果。 |
所以能够这样解决问题:
.content {overscroll-behavior: none;}
简洁洁净高性能,不过 Safari 全系不反对,兼容性如下,有没有感觉 Safari 就是现代版的 IE(偶尔听路人说的)!
✅ 解决方案 B (event.preventDefault)
借用 event.preventDefault 的能力,当组件滚动到底部或顶部时,通过调用 event.preventDefault 阻止所有滚动,从而页面滚动也不会触发了,而在滚动之间则不做解决。
let initialPageY = 0
scrollBox.addEventListener('touchstart', (e) => {initialPageY = e.changedTouches[0].pageY
})
scrollBox.addEventListener('touchmove', (e) => {const deltaY = e.changedTouches[0].pageY - initialPageY
// 禁止向上滚动溢出
if (e.cancelable && deltaY > 0 && scrollBox.scrollTop <= 0) {e.preventDefault()
}
// 禁止向下滚动溢出
if (
e.cancelable &&
deltaY < 0 &&
scrollBox.scrollTop + scrollBox.clientHeight >= scrollBox.scrollHeight
) {e.preventDefault()
}
})
解决方案残缺 Demo
https://github.com/Barrior/ca…