移动端tap与click的区别 && 点透事件
来源: 博客园 - Wayne-Zhu
1. 移动端的问题
移动端的主要问题是 click 事件会有 300ms的延迟 。
主要原因是苹果手机在设计时,考虑到用户在浏览网页时需要双击放大,所以,在用户点击的 300ms 之后,才触发 click 事件。如果 300ms 之内还有 click,就会进行放大缩小。
但是,大部分时候放大、缩小是不需要的,开发者也会禁用它们,那么这 300ms 的延迟就是性能上的损耗。
如何解决这300ms的延迟?
在移动端,最容易想到的就是使用 touchend 来替代 click。但是 touchend 是存在很大问题的,因为 touchend 之前可能是 touchstart、touchmove。具体情境可能是:用户滑动页面时,不小心在一个按钮那里触发了 touchend,这样就执行了,但是用户的本意不是如此。
那么该怎么解决呢?
2. tap事件
为了减少这 300ms 的延迟,tap 事件被很多框架(如 Zepto)封装,用来减少延迟问题。tap 事件不是原生的,是封装出来的。
具体是如何实现的呢?主要考虑到下面两点:
- 按住的时间不能超过延时时间:因为长时间按住可能就是浏览器的复制、粘贴等操作了。
- 不能在页面中移动 :移动是不能触发
tap事件的。
我们可以自己封装如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>tap</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<button id="btn">按钮</button>
<script>
function tap(ele, callback) {
// 记录开始时间
var startTime = 0,
// 控制允许延迟的时间
delayTime = 200,
// 记录是否移动,如果移动,则不触发tap事件
isMove = false;
// 在touchstart时记录开始的时间
ele.addEventListener('touchstart', function (e) {
startTime = Date.now();
});
// 如果touchmove事件被触发,则isMove为true
ele.addEventListener('touchmove', function (e) {
isMove = true;
});
// 如果touchmove事件触发或者中间时间超过了延迟时间,则返回,否则,调用回调函数。
ele.addEventListener('touchend', function (e) {
if (isMove || (Date.now() - startTime > delayTime)) {
return;
} else {
callback(e);
}
})
}
var btn = document.getElementById('btn');
tap(btn, function () {
alert('taped');
});
</script>
</body>
</html>
如上,我们就可以正常使用 tap 事件并且避免了 300ms 延迟的产生。
3. 点透问题 (Ghost Click)
如果我们在移动端所有的 click 都替换为了 tap 事件,还是会触发点透问题。
实质是:
在同一个 z 轴上,z-index 不同的两个元素,上面的元素是一个绑定了 tap 事件的,下面是一个 a 标签。一旦 tap 触发,上面的元素就会 display: none(隐藏或消失)。
从上面的 tap 可以看出,有 touchstart、touchend,所以系统会在 300ms 之后触发 click 事件。此时,上层元素的 z-index 已经消失了,所以,触发了下面 a 标签的 click 事件 (注意:认为 a 标签默认是绑定了 click 事件的)。而这种现象不是我们所期待的。
4. 解决方案
解决点透问题通常有以下两种方案:
(1) 使用 fastclick 库
直接引入 fastclick 库,可以有效解决 300ms 延迟和点透问题。
javascript
window.addEventListener("load", function () {
FastClick.attach(document.body);
}, false);
这样就可以成功解决问题了。
(2) 添加一个延迟
对于上一个 tap 事件做延迟处理,等 300ms 之后再隐藏上层元素。
javascript
tap(ele, function () {
setTimeout(function () {
ele.style.display = 'none';
}, 300);
})
这样,过了 300ms 后,click 事件就不会触发在下面的 a 标签上了。