业务场景描述
组内有一个自制的vue弹框组件,就是出一个弹框然后周围都是半透明黑色遮罩的那种。之前的业务场景中弹框中的内容都不需要滚动,因此滚动穿透问题并没有得到重视,然后这个坑我就掉进去了……前人挖坑后人跳坑(确信。组件中原本的解决方案是在组件外层添加一个事件监听,阻止默认的touchmove事件冒泡,这样弹框中需要滚动的部分也不能滚动了,而且好像在iOS上也对穿透到body滚动的问题没啥效果。
尝试解决方案一
在原有的方案上做改进,传入一个class白名单,发现白名单的子元素或自身(使用contains
方法判断)触发的touchmove事件就不阻止默认动作了,然后在要滚动的白名单class元素上增加CSSoverscroll-behavior: none;
规则,使其冒泡到顶层的事件不能触发外层的浏览器默认动作。
问题
只在新版Android的chrome上有用,因为这个属性不能在Safari上生效,在IE10及以上上这个规则都有私有实现……迫真新时代IE6,可用列表详见caniuse。
尝试解决方案二
在询问谷歌老师以后发现了文章移动端滚动穿透问题,文章进行了较好的总结,在其最后提供的方法上进行了小小的改进。在屏蔽滚动时,项目原有的body宽度规则会被出现问题,于是手动指定了在屏蔽和恢复时的body的宽度。
// 打开弹窗时屏蔽滚动
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
document.body.style.cssText += `position: fixed;top: -${scrollTop}px;width: 100vw;`;
// 关闭弹窗时恢复滚动
const body = document.querySelector('body');
body.style.position = '';
// 注意恢复原有宽度
body.style.width = '100%';
const top = parseInt(body.style.top, 10);
// 重置body滚动高度
document.body.scrollTop = -top;
// 重置html滚动高度
document.documentElement.scrollTop = -top;
body.style.top = '';
尝试解决方案三
以上解决方案有一个问题就是在iOS上,似乎是会把滚动事件绑定到“手指焦点”的元素上,如果用户手指按到不需要滚动的元素上,还要手动把焦点切回去才能继续滚动滚动。在继续询问谷歌老师后发现了这篇文章移动端踩坑之滚动穿透,这位老哥提出了
touchmove 事件中调用 preventDefault,将 EventTarget 从 body 改为日期控件的dom。
这个解法,而且因为我现在的组件中已经有个之前尝试填坑用的可滚动白名单class。于是在这位老哥的基础上,在body上添加新的监听,把不在白名单的dom触发的touchmove事件全部重新分发到白名单dom上,目前效果似乎还不错?(不要忘记在关闭的时候移除这个事件的监听
const touchmove = document.createEvent('TouchEvent');
// 可以冒泡,可以被取消
touchmove.initEvent('touchmove', true, true);
movableNode.dispatchEvent(touchmove);