0%

“点击穿透”的解决方案

我们这里所说的点击穿透,指的是在移动端中可能会出现的现象,对于移动端web开发人员来说,应该会碰到这么一个问题,那就是,假设我们屏幕上有一个遮罩,它绑定了touch事件,并且遮罩的后方存在一个绑定有click事件的元素。

现在我有一个需求,点击遮罩时,要关闭这个遮罩,让这个遮罩消失,直接 display: none 掉。

但实际上,当我点击这个遮罩时,它身后那个元素身上的click也会被触发,有时候这并不是我们想要的,那么我们应该怎么解决,又有多少种解决方案呢?

形成条件

为什么要提 形成条件,因为 点击穿透现象不是那么容易形成的,需要同时满足以下三个条件,才会出现点击穿透现象:

  1. 你点击的那个元素绑定了 touch 事件
  2. 你点击的那个元素的后方,存在着其他绑定了 click 事件的元素
  3. 你点击的那个元素在触发 touch 事件后,它自己或它的祖先元素立刻从页面中消失了

如果你不满足上面的全部条件,那就没办法复现。


初始代码

<style>
#box {
width: 100%;
height: 300px;
background-color: red;
}
#mask {
position: absolute;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, .5);
}
#btn {
margin-top: 50px;
}
</style>

<div>
<a id="box">xxx</a>
<div id="mask">
<button id="btn">点击关闭遮罩</button>
</div>
</div>

<script>
let box = document.getElementById("box");
let mask = document.getElementById("mask");
let btn = document.getElementById("btn");

box.addEventListener("click", () => {
window.location.href = "https://xxx.com";
})

btn.addEventListener("touchstart", () => {
mask.style.display = "none";
})
</script>

方案一

阻止事件的默认行为。

btn.addEventListener("touchstart", e => {
e.preventDefault();
mask.style.display = "none";
// ...
})

这是最简单最快速的方案!

适用于:你不依赖点击穿透的特性(某些人 就是故意使用点击穿透的特性来做一些 狗都做不出来的事


方案二

给背后绑定了click事件的元素添加 pointer-events 样式

这条样式的作用是:控制元素是否可以触发事件

/* 禁止 .box 这个元素触发事件 */
#box { pointer-events: none }

然后,在你的touch事件函数里,对这条样式进行延迟重置

btn.addEventListener("touchstart", e => {
mask.style.display = "none";
setTimeout(() => {
// 让 .box 可以触发事件
box.style.pointerEvents = "auto";
}, 350)
})

说明一下,为什么我定时器里写 350ms,如果你看过我 “移动端事件” 的文章,你就会知道:

PC端事件的响应速度会被移动端慢300ms左右,而我们背后的那个元素绑定的是PC端事件click。


方案三

使背后的元素,不具备 click 特性。

我这里说的是特性,什么叫 click 特性,click 代表点击。

一个元素是否可以点击,是不是由 元素特有 或者 绑定事件 才能实现?

例如 a标签 天生就可以点击,这是它的 特性

一个 div 天生就不能点击,但是我们给他绑定了 click 事件,也就赋予了它这个特性。

重新回来,刚刚我们说,解决方案是:使背后的元素,不具备 click 特性

如何才能做到呢?

答案如下:

  1. 不给它绑定 click 事件(可以替换成别的事件)
  2. 把可以点击的元素替换掉,例如 a标签 替换为 div标签
<div>
<!-- box使用div标签,它不存在click特性 -->
<div id="box"></div>
<div id="mask">
<button id="btn">点击关闭遮罩</button>
</div>
</div>
// box之前绑定的是click,现在我们给它替换成touchstart
box.addEventListener("touchstart", () => {
window.location.href = "https://xxx.com";
})

方案四

点击时,让元素延迟消失

btn.addEventListener("touchstart", e => {
setTimeout(() => {
mask.style.display = "none";
}, 350)
})

为什么延迟消失也能解决?

定时器的回调函数是 异步 的,异步代码不会立即执行。

所以,js引擎会先把定时器丢到事件队列内进行等待,随后它再向后找,并喊道:”还有没有click事件!!!“

没人回应js引擎,所以它就走了,它去执行事件队列中的东西去了。

所以,就不会再触发点击穿透的效果。

但是这个也有缺点,你延迟关闭遮罩,会给用户造成一种卡顿,缓慢的感觉,影响不是太好。

-------------本文结束    感谢阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!