滑动穿透的表现
最近有一个需求需要在移动端页面内嵌一个 iframe 页面来展示信息。把 iframe 放在组件库的 Panel 组件的蒙层中,发现滑动 iframe 时,会导致 iframe 后的主页面发生页面滚动。这就是滑动穿透。
解决方案
看了网上的解决方案,有的是修改 body 的 css 样式来实现的。这种方式比较麻烦,且很容易出错。
目前较优的解决方案是通过 preventEvent 来禁止 touchmove 事件。
核心思路:能滚动的元素,不禁用 touchmove 事件;不能滚动的元素,禁用 touchmove 事件。
流程图
graph LR
A(开始)-->B[触发 touchmove 事件]-->C[获取事件的 element list]-->D{判断是否有元素可滚动}
D--yes-->E[不阻止 touchmove 事件]
D--no-->F[阻止 touchmove 事件]
代码
// 只需要把想阻止滚动穿透的元素传入即可
const preventScrollPenetrate = (container: HTMLDivElement | null) => {
if (!container) {
return;
}
const handleTouchMove = (event: TouchEvent) => {
// 获取事件触发的整个元素列表
const elements = event.composedPath() as HTMLElement[];
// 最后一个元素是 Window ,需要删除,否则 contains 判断会报错
elements.pop();
let shouldPrevent = true;
// 判断是否有元素可滚动,可滚动不阻止事件
for (const elem of elements) {
if (container.contains(elem) && elem.clientHeight !== elem.scrollHeight) {
const style = getComputedStyle(elem);
// 获取元素的 style ,overflow 为 hidden 也是不可滚动状态
if (style.overflow === 'hidden') {
continue;
}
shouldPrevent = false;
break;
}
}
if (shouldPrevent) {
event.preventDefault();
}
};
container.addEventListener('touchmove', handleTouchMove, { passive: false });
// 返回取消监听函数
return () => {
container.removeEventListener('touchmove', handleTouchMove);
};
};