移动端点击事件延迟

移动端的点击事件的有延迟,时间是多久,为什么会有? 怎么解决这个延时?

  • 延时时间
    • 300ms
  • 为何存在
    • Safari 双击缩放事件
      • 为了判断用户是想点击链接还是双击缩放,所以等待300ms
      • 300ms内再次点击,算双击;300ms外算单击
  • 如何解决
    • meta标签设置全局禁止缩放
      • <meta name="viewport" content="user-scalable=no,initial-scale=1,maximun-scale=1"/>
    • css touch-action: none 仅IE支持
      • 指定相应的元素上能够触发的用户代理(浏览器)的默认行为。设置none就是阻止默认行为,就不用再进行300ms的判断了
    • 封装 tap 函数
      • 原理
        • 监听 touchstarttouchend 事件,记录两者触发时间,如果小于 150ms 且在此过程中没有滑动过屏幕,就当做click事件,然后执行回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button class="btn">点我</button>
<script>
/**
* 封装tap 解决移动端click事件300ms延时问题
* @param ele 监听元素
* @param cb 执行的回调函数
*/
function tap(ele, cb) {
let startTime = 0, // 触摸开始时间
isMove = false; // 是否有滑动
ele.addEventListener('touchstart', () => {
// 记录开始触摸时间
startTime = Date.now();
});
ele.addEventListener('touchmove', () => {
// 有滑动 算拖拽 不算点击
isMove = true;
});
ele.addEventListener('touchend', () => {
// 从 touchstart 到 touchend 时间不超过 150ms 且没有滑动屏幕
// 就算 click 事件
if (Date.now() - startTime < 150 && !isMove) {
cb && cb();
}
// 重置变量
startTime = 0;
isMove = false;
})
}

// 调用
tap(document.getElementsByClassName('btn')[0], () => {
console.log('click');
})
</script>
</body>
</html>
  • FastClick 库
    • 原理
      • 检测到 touchend 事件后,会通过DOM自定义事件来模拟click事件,并把浏览器在300ms后的真正的click事件阻止掉
1
2
3
4
5
6
7
8
9
10
 // 消除所有元素click延时
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
// 当使用click事件时 不会再有300ms延时
div.addEventListener('click',function () {
// 业务处理
})

能不能直接用 touchstart 代替 click?

不能,使用 touchstart 去代替 click 事件有两个不好的地方

  1. touchstart是手指触摸屏幕就触发,有时候用户只是想滑动屏幕, 却触发了touchstart事件,这不是我们想要的结果
  2. 使用touchstart事件在某些场景下可能会出现点击穿透的现象
    1. 点击穿透现象:
      1. 假如页面上有两个元素A和B。B元素在A元素之上。我们在B元素的touchstart事件上注册了一个回调函数,该回调函数的作用是隐藏B元素。我们发现,当我们点击B元素,B元素被隐藏了,随后,A元素触发了click事件。
      2. 这是因为在移动端浏览器,事件执行的顺序是touchstart > touchend > click。而click事件有300ms的延迟,当touchstart事件把B元素隐藏之后,隔了300ms,浏览器触发了click事件,但是此时B元素不见了,所以该事件被派发到了A元素身上。如果A元素是一个链接,那此时页面就会意外地跳转。

场景重现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no" />
<title></title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}

.item {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 200px;
background: pink;
opacity: .5;
}
</style>
</head>

<body>
<div class="item"></div>
<a href="http://www.bilibili.com">B站</a>
</body>
<script type="text/javascript">
window.onload = function () {
/*
1.pc端的事件可以在移动端触发
2.PC端事件有300毫秒延迟
3.移动端事件不会有延迟

当点击item和a标签重叠的部分时
首先触发了移动端没有延迟的touchstart事件,
调用了该事件的回调函数,隐藏了item
接着300ms延迟以后原地触发了pc端click事件,
此时由于item已经消失,所以“点”在了a标签上,
导致跳转页面
*/
var item = document.querySelector(".item");
var a = document.querySelector("a");
item.addEventListener("touchstart", function () {
console.log("touchstart");
this.style.display = "none";
})
a.addEventListener("click",function(){
console.log("click");
})

}
</script>

</html>

解决方案

fastclick.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no" />
<title></title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}

.item {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 200px;
background: pink;
opacity: .5;
}
</style>
</head>

<body>
<div class="item"></div>
<a href="http://www.atguigu.com">回娘家</a>
<a href="http://www.atguigu.com">回娘家</a>
<a href="http://www.atguigu.com">回娘家</a>
<a href="http://www.atguigu.com">回娘家</a>
<a href="http://www.atguigu.com">回娘家</a>
<a href="http://www.atguigu.com">回娘家</a>
<a href="http://www.atguigu.com">回娘家</a>
</body>
<script src="https://cdn.bootcss.com/fastclick/1.0.6/fastclick.min.js"></script>
<script type="text/javascript">
// 引入 fastclick.js
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
window.onload = function () {
var item = document.querySelector(".item");
var a = document.querySelector("a");
item.addEventListener("touchstart", function () {
console.log("touchstart");
this.style.display = "none";
})
a.addEventListener("click", function () {
console.log("click");
})

}
</script>

</html>