移动端在touch上一共有4个事件
touchstart touchmove touchend touchcancel, touchcancel, 一般来说,它们执行的顺序为 touchstart -> touchmove -> touchend -> touchcancel . 其中touchcancel一般情况下不会触发,也不是这里讨论的焦点。
这里会结合click对上面的事件进行讨论, touch发生在click之前
先上段代码,直观感受一下
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#level0 {
/* width: 500px;
height: 500px; */
}
#level1-0 {
background: red;
width: 500px;
height: 500px;
}
#level1-1 {
background: green;
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<div id="level0">
<div id="level1-0">
</div>
<div id="level1-1">
</div>
</div>
</body>
<script type="text/javascript">
var level10 = document.getElementById("level1-0");
level10.addEventListener('touchstart', function(e) {
console.log(1);
});
level10.addEventListener('touchmove', function(e) {
console.log(2);
});
level10.addEventListener('touchend', function(e) {
console.log(3);
});
level10.onclick = function() {
console.log(5);
}
document.body.onclick = function() {
console.log('6');
}
</script>
</html>
在红色区域点击会出现什么效果呢? 出现的是 1 3 5 6, 奇怪了 touchmove 为何不执行,因为我们并没有移动,也就是说,必须触碰到屏幕上面,而且发生了移动动作,touchmove才执行,现在我们触碰到,而且手指稍微动一下,发现输出的效果是, 1 2(+) 3, 其中touchmove 可能触发多次,又奇怪了, click为何不执行, 因为 click执行的条件是 点击, 而且不移动 所以一般情况下,我们可以理解成 touchmove和click是相斥的。
我们知道,当一个用户在点击屏幕的时候,系统会触发touch事件和click事件,touch事件优先处理,touch事件经过 捕获,处理, 冒泡 一系列流程处理完成后, 才回去触发click事件
既然touch事件和click事件有了优先级别,那么能不能在touch阶段取消掉系统触发的click事件呢?当然是可以的,浏览器提供了这样的能力。在touch事件里面,调用e.preventDefault() 就可以阻止本次点击系统触发的click事件,即本次相关的click都不会执行
把上面代码稍微加一点
level10.addEventListener('touchstart', function(e) {
console.log(1);
e.preventDefault();
});
点击的时候 发现 只有 1 3, 说明click被阻止了,当然在touchend里面加效果也一样,所以 在touch事件里面加 e.preventDefault可以取消系统产生的click事件, 当然不会阻止后面的touch事件。
用个具体的例子看看 如何解决点透问题
产生点透问题的原因, 可以先看看代码吧
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#level0 {
/* width: 500px;
height: 500px; */
position: relative;
}
#level1-0 {
position: absolute;
z-index: 1;
background: red;
width: 500px;
height: 500px;
}
#level1-1 {
background: green;
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<div id="level0">
<div id="level1-0">
</div>
<div id="level1-1">
</div>
</div>
</body>
<script type="text/javascript">
var level10 = document.getElementById("level1-0");
var level11 = document.getElementById("level1-1");
level10.addEventListener('touchstart', function(e) {
level10.style.display = 'none';
});
level11.onclick = function() {
console.log('level11莫名被点击了');
}
</script>
</html>
本来是 level1-0 和 level1-1是兄弟节点,即他们之间不会发生什么 事件传递, 目前level1-0相当于一个覆盖层,覆盖在level1-1上面, 按理说点击 level1-0的时候,level1-0会阻挡所有的事件,事件不会传递给level1-1,当点击level1-0的时候,实际上level1-1也发生了点击事件,即上面的输出结果为
level1-0消失, 输出 level11莫名被点击了
, 这就是点透
点透发生的条件,
- A 和 B不是后代继承关系(如果是后代继承关系的话,就直接是冒泡子类的话题了)
- A发生touch, A touch后立即消失, B事件绑定click
- A z-index大于B,即A显示在B浮层之上
点透发生的理由: 当手指触摸到屏幕的时候,系统生成两个事件,一个是touch 一个是click,touch先执行,touch执行完成后,A从文档树上面消失了,而且由于移动端click还有延迟200-300ms的关系,当系统要触发click的时候,发现在用户点击的位置上面,目前离用户最近的元素是B,所以就直接把click事件作用在B元素上面了
.
那如何才能解决点透问题呢? 还记得我之前说过么,系统提供了先触发的touch事件去取消系统生成的click事件,所以只要在touch事件的某个处理函数中 执行 e.preverDefault即可, 一般我们在touchend中执行
在上面代码中,加上这句就完美解决了
level10.addEventListener('touchend', function(e) {
e.preventDefault();
});
当然点透问题,还有其他的解决方法,关键是 要么是需求本次系统生成的click事件,要么是当系统触发click的时候,当前的触发touch的那个dom节点还存在。比如将其一延迟3s在关闭
setTimeout(() => {
level10.style.display = 'none';
}, 300);