纯CSS属性pointer-events:none解决滚动穿透问题

2019-11-11 19:06:19

·什么是滚动穿透

在移动端的前端开发中,我们常常会用到Modal弹窗,又称模态框,用来在已有页面显示新的选项、提示或新内容。遮罩层常出现在弹窗后,用来在视觉上,帮助用户集中注意力,功能上,防止用户继续操作页面上的其他内容。遮罩层通常是一个绝对定位,宽高充满内容区域或可视区域,层级略低于弹窗的具有一定透明度的背景。

在Web端,我们只需要通过CSS设置上述属性,就可以完成实现“遮罩实现”的功能,但当用户使用鼠标滚轮,或者在移动端,通过手指滑动时,我们发现:

位于遮罩层背后的内容,穿透了我们设置的遮罩层,跟随用户的操作发生滚动。这种现象,就被称为“滚动穿透”。

·你可能会想到的解决滚动穿透问题的方法

看到这种由非直接目标对象响应事件的现象,很多人都会想到 Js的事件处理机制,进而想到,阻止事件冒泡,避免对Modal弹窗层的操作,冒泡到遮罩层下。

然而这种思路并不总是行得通,冒泡的前提,是两个对象具有父子关系,事件从子对象传递到父对象,然而我们的弹出层经常被直接append到body或根节点中,与原有内容在一个层级,用阻止冒泡的方法并不能达成目的。

事实上,位于遮罩层下方的可滚动元素和上方的弹出层,由于重叠,直接响应了用户的操作,而不是接受上层的事件传递。

那我们可不可以阻止默认事件呢?

通过preventDefault()阻止默认事件,任何通常被该实现触发并作为结果的默认行为都不会发生,上层和下层元素的默认滚动行为都将被阻止,这显然并不是我们想要的。

此外,还有很都方法,通过十几行甚至几十行代码,在移动端,阻止滚动穿透,有点大材小用啦。

·化繁为简,一行CSS3属性解决滚动穿透问题

让我们先认识这个CSS3属性 pointer-events,它的默认属性值是auto,而对于前端开发,另一个值 none,显然更有用。通过设置:


pointer-events: none

> 元素永远不会成为鼠标事件的target。但是,当其后代元素的pointer-events属性指定其他值时,鼠标事件可以指向后代元素,在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器。

将MDN的解释形象化,你可以把设置 pointer-events: none 看成一张透明纸,它本身不再响应鼠标事件(事实上,也包括移动的手指触摸Touch事件),却可以传递事件,让事件穿过它自己,继续传到父级。


简而言之,我们想让哪层不滚动,只需要给这层设置 pointer-events: none 。

配合弹窗的状态,当弹窗显示的时候,给位于遮罩层底下的图层设置  pointer-events: none ,当弹窗隐藏/关闭的时候,设置  pointer-events: auto ,或者干脆移除这个属性。


pointer-events 是一个比较早公布的CSS3属性,在Web端和移动端都有良好的支持度:

CSS3的属性pointer-events的浏览器和移动端兼容性图示