移动端h5页⾯touch事件与点击穿透问题
前⾔
做过移动端H5页⾯的同学肯定知道,移动端web的事件模型不同于PC页⾯的事件。看了⼀些关于touch事件的⽂章,我想再来回顾下touch 事件的原理,为什么通过touch可以触发click事件,touch事件是不是万能的以及它可能存在的问题。
touch事件的来源
PC⽹页上的⼤部分操作都是⽤⿏标的,即响应的是⿏标事件,包括mousedown、mouseup、mousemove和click事件。⼀次点击⾏为,可被拆解成:mousedown -> click -> mouseup三步。
⼿机上没有⿏标,所以就⽤触摸事件去实现类似的功能。touch事件包含touchstart、touchmove、touchend,注意⼿机上并没有tap事件。⼿指触发触摸事件的过程为:touchstart -> touchmove -> touchend。
⼿机上没有⿏标,但不代表⼿机不能响应mouse事件(其实是借助touch去触发mouse事件)。有⼈在PC和⼿机上对事件做了对⽐实验,以说明⼿机对touch事件相应速度快于mouse事件。
可以看到在⼿机上,当我们⼿触碰屏幕时,要过300ms左右才会触发mousedown事件,所以click事件在⼿机上看起来就像慢半拍⼀样。touch事件中可以获取以下参数
参数含义
touches屏幕中每根⼿指信息列表
targetTouches和touches类似,把同⼀节点的⼿指信息过滤掉
changedTouches响应当前事件的每根⼿指的信息列表
tap是怎么来的
⽤过Zepto或KISSY等移动端js库的⼈肯定对tap事件不陌⽣,我们做PC页⾯时绑定click,相应地⼿机页⾯就绑定tap。但原⽣的touch事件本⾝是没有tap的,js库⾥提供的tap事件都是模拟出来的。
我们在上⾯看到,⼿机上响应 click 事件会有300ms的延迟,那么这300ms到底是⼲嘛了?浏览器在 touchend 后会等待约300ms,原因是判断⽤户是否有双击(double tap)⾏为。如果没有 tap ⾏为,则触发 click 事件,⽽双击过程中就不适合触发 click 事件了。由此可以看出click 事件触发代表⼀轮触摸事件的结束。
既然说tap事件是模拟出来的,我们可以看下Zepto对 singleTap 事件的处理。,可以看出在 touchend 响应 250ms ⽆操作后,则触发singleTap。
点击穿透的场景
有了以上的基础,我们就可以理解为什么会出现点击穿透现象了。我们经常会看到“弹窗/浮层”这种东西,我做个了个demo。
整个容器⾥有⼀个底层元素的div,和⼀个弹出层div,为了让弹出层有模态框的效果,我⼜加了⼀个遮罩层。
<div class="container">
<div id="underLayer">底层元素</div>
svg和h5的关系<div id="popupLayer">
<div class="layer-title">弹出层</div>
<div class="layer-action">
<button class="btn" id="closePopup">关闭</button>
</div>
</div>
</div>
<div id="bgMask"></div>
然后为底层元素绑定 click 事件,⽽弹出层的关闭按钮绑定 tap 事件。
$('#closePopup').on('tap', function(e){
$('#popupLayer').hide();
$('#bgMask').hide();
});
$('#underLayer').on('click', function(){
alert('underLayer clicked');
});
点击关闭按钮,touchend⾸先触发tap,弹出层和遮罩就被隐藏了。touchend后继续等待300ms发现没有其他⾏为了,则继续触发click,由
于这时弹出层已经消失,所以当前click事件的target就在底层元素上,于是就alert内容。整个事件触发过程为 touchend -> tap -> click。
⽽由于click事件的滞后性(300ms),在这300ms内上层元素隐藏或消失了,下层同样位置的DOM元素触发了click事件(如果是input框则会触发focus事件),看起来就像点击的target“穿透”到下层去了。
请⽤chrome⼿机模拟器查看,或直接扫描⼆维码在⼿机上查看。
结合Zepto源码的解释
中的 tap 通过兼听绑定在 document 上的 touch 事件来完成 tap 事件的模拟的,是通过事件冒泡实现的。在点击完成时(touchstart / touchend)的 tap 事件需要冒泡到 document 上才会触发。⽽在冒泡到 document 之前,⼿指接触和离开屏幕(touchstart / touchend)是会触发 click 事件的。
因为 click 事件有延迟(⼤概是300ms,为了实现safari的双击事件的设计),所以在执⾏完 tap 事件之后,弹出层⽴马就隐藏了,此时 click 事件还在延迟的 300ms 之中。当 300ms 到来的时候,click 到的其实是隐藏元素下⽅的元素。
如果正下⽅的元素有绑定 click 事件,此时便会触发,如果没有绑定 click 事件的话就当没发⽣。如果正下⽅的是 input 输⼊框(或是 select / radio / checkbox),点击默认 focus ⽽弹出输⼊键盘,也就出现了上⾯的“点透”现象。
穿透的解决办法
1. 遮挡
由于 click 事件的滞后性,在这段时间内原来点击的元素消失了,于是便“穿透”了。因此我们顺着这个思路就想到,可以给元素的消失做⼀个fade效果,类似jQuery⾥的fadeOut,并设置动画duration⼤于300ms,这样当延迟的 click 触发时,就不会“穿透”到下⽅的元素了。
同样的道理,不⽤延时动画,我们还可以动态地在触摸位置⽣成⼀个透明的元素,这样当上层元素消失⽽延迟的click来到时,它点击到的是那个透明的元素,也不会“穿透”到底下。在⼀定的timeout后再将⽣成的透明元素移除。
2. pointer-events
pointer-events是CSS3中的属性,它有很多取值,有⽤的主要是auto和none,其他属性值为SVG服务。
取值含义
auto效果和没有定义 pointer-events 属性相同,⿏标不会穿透当前层。
none元素不再是⿏标事件的⽬标,⿏标不再监听当前层⽽去监听下⾯的层中的元素。但是如果它的⼦元素设置了pointer-events为其它值,⽐如auto,⿏标还是会监听这个⼦元素的。
关于使⽤ pointer-events 后的事件冒泡,有⼈做了个实验,
因此解决“穿透”的办法就很简单,
$('#closePopup').on('tap', function(e){
$('#popupLayer').hide();
$('#bgMask').hide();
$('#underLayer').css('pointer-events', 'none');
setTimeout(function(){
$('#underLayer').css('pointer-events', 'auto');
}, 400);
});
3. fastclick
使⽤库,其实现思路是,取消 click 事件(),⽤ touchend 模拟快速点击⾏为()。
FastClick.attach(document.body);
从此所有点击事件都使⽤click,不会出现“穿透”的问题,并且没有300ms的延迟。
有⼈(叶⼩钗)对事件机制做了详细的剖析,循循善诱,并剖析了fastclick的源码以⾃⼰模拟事件的创建。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论