移动端点击穿透问题
移动端触摸事件触发 click 导致的点击穿透现象及解决方案
问题
移动端触摸元素后,约 300ms 会触发 click 事件,导致触发了下层元素的点击行为,这种现象称为点击穿透。
解答
发生原因
移动端触摸事件(touch)触发后,系统会延迟约 300ms 模拟产生 click 事件。如果触摸后上层元素消失,click 事件会作用到下层元素上。
触发条件
- 上层元素监听了触摸事件,触摸后该元素消失
- 下层元素具有点击特性(监听了 click 事件,或是
<a>、<input>、<button>等标签)
常见场景
场景一:蒙层穿透
点击蒙层上的关闭按钮,蒙层消失后触发了下方元素的 click 事件。
场景二:触发链接跳转
点击按钮后元素消失,下方恰好是 <a> 标签,页面发生跳转。
场景三:跨页面穿透
点击按钮跳转到新页面,新页面中相同位置的元素 click 事件被触发。
场景四:连续跳转
新页面对应位置恰好是 <a> 标签,发生连续跳转。
解决方案
方案一:统一事件类型
不要混用 touch 和 click 事件,全部使用 touch 或全部使用 click。
// 只使用 click
button.addEventListener('click', () => {
mask.style.display = 'none';
});
// 或只使用 touchend
button.addEventListener('touchend', (e) => {
e.preventDefault(); // 阻止后续 click
mask.style.display = 'none';
});
方案二:阻止默认行为
在 touchend 事件中调用 preventDefault()。
button.addEventListener('touchend', (e) => {
e.preventDefault();
mask.style.display = 'none';
});
方案三:延迟隐藏
延迟 350ms 后再隐藏上层元素。
button.addEventListener('touchend', () => {
setTimeout(() => {
mask.style.display = 'none';
}, 350);
});
方案四:pointer-events
临时禁用下层元素的点击。
button.addEventListener('touchend', () => {
mask.style.display = 'none';
document.body.style.pointerEvents = 'none';
setTimeout(() => {
document.body.style.pointerEvents = 'auto';
}, 350);
});
方案五:遮挡层
用透明遮挡层拦截 click 事件。
button.addEventListener('touchend', () => {
mask.style.display = 'none';
const shield = document.createElement('div');
shield.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999';
document.body.appendChild(shield);
setTimeout(() => {
document.body.removeChild(shield);
}, 350);
});
关键点
- 点击穿透源于移动端 touch 事件后 300ms 触发 click 事件
- 避免混用 touch 和 click 事件,统一使用一种事件类型
- 使用
preventDefault()阻止 touch 事件后的 click 触发 - 可通过延迟隐藏、pointer-events 或遮挡层等方式消费掉多余的 click 事件
目录