zepto学习(二)之tap事件以及tap事件点透处理

前言

为什么通过touch可以触发click事件?

touch事件的来源

PC网页上的大部分操作都是用鼠标的,即响应的是鼠标事件,包括mousedownmouseupmousemoveclick事件。一次点击行为,事件的触发过程为:mousedown -> mouseup -> click 三步。

手机上没有鼠标,所以就用触摸事件去实现类似的功能。touch事件包含touchstarttouchmovetouchend,注意手机上并没有tap事件。手指触发触摸事件的过程为:touchstart -> touchmove -> touchend

手机上没有鼠标,但不代表手机不能响应mouse事件(其实是借助touch去触发mouse事件)。有人在PC和手机上对事件做了对比实验,以说明手机对touch事件响应速度快于mouse事件。

可以看到在手机上,当我们手触碰屏幕时,要过300ms左右才会触发mousedown事件,所以click事件在手机上看起来就像慢半拍一样。

tap是怎么来的

我们在上面看到,手机上响应 click 事件会有300ms的延迟,那么这300ms到底是干嘛了?浏览器在 touchend 后会等待约300ms,原因是判断用户是否有双击(double tap)行为(主要原因是苹果手机在设计时,考虑到用户在浏览网页时需要放大)。如果没有 tap 行为,则触发 click 事件,而双击过程中就不适合触发 click 事件了。由此可以看出 click 事件触发代表一轮触摸事件的结束。

既然说tap事件是模拟出来的,我们可以看下Zepto对 singleTap 事件的处理。见源码 136-143 行,可以看出在 touchend 响应 250ms 无操作后,则触发singleTap。

注意:singleTap和doubleTap分别代表单次点击和双次点击。

点击穿透的场景

即点击会触发非当前层的点击事件。

有了以上的基础,我们就可以理解为什么会出现点击穿透现象了。我们经常会看到“弹窗/浮层”这种东西,我做个了个demo。

整个容器里有一个底层元素的div,和一个弹出层div,为了让弹出层有模态框的效果,我又加了一个遮罩层。

<div class="container">

    <div id="underLayer">底层元素</div>

 

    <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“穿透”到下层去了。

结合Zepto源码的解释

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 而弹出输入键盘,也就出现了上面的“点透”现象。

穿透的解决办法

穿透的原因其实就是tap事件先于click事件300ms执行

1. 遮挡

我们可以动态地在触摸位置生成一个透明的元素,这样当上层元素消失而延迟的click来到时,它点击到的是那个透明的元素,也不会“穿透”到底下。在一定的timeout后再将生成的透明元素移除。具体可见demo

2. pointer-events

pointer-events是CSS3中的属性,它有很多取值,有用的主要是autonone,其他属性值为SVG服务。

关于使用 pointer-events 后的事件冒泡,有人做了个实验,见代码

因此解决“穿透”的办法就很简单,对下面的元素设置pointer-eventsdemo如下

$('#closePopup').on('tap', function(e){

    $('#popupLayer').hide();

    $('#bgMask').hide();

 

    $('#underLayer').css('pointer-events', 'none');

    setTimeout(function(){

        $('#underLayer').css('pointer-events', 'auto');

    }, 400);

 });

 

 3. fastclick

使用fastclick库,其实现思路是,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后真正的click事件阻止掉(参看源码 164-173 行),用 touchend 模拟快速点击行为(参看源码 521-610 行)。

window.addEventListener("load", function () {
   FastClick.attach(document.body);
}, false);

从此所有点击事件都使用click来绑定,不会出现“穿透”的问题,并且没有300ms的延迟。解决穿透的demo

有人(叶小钗)对事件机制做了详细的剖析,循循善诱,并剖析了fastclick的源码以自己模拟事件的创建。请看这篇文章,看完后一定会对移动端的事件有更深的了解

  • 注意点: 框架必须在前面调用, 并且所有的元素都被注册了fastclick中的事件, 以后所有的click事件都是fastclick的click事件

4. 延迟

方法一:延时动画

由于 click 事件的滞后性,在这段时间内原来点击的元素消失了,于是便“穿透”了。因此我们顺着这个思路就想到,可以给元素的消失做一个fade效果,类似jQuery里的fadeOut,并设置动画duration大于300ms,这样当延迟的 click 触发时,就不会“穿透”到下方的元素了。

同样的道理,不用延时动画,

方法二:对tap做延迟

tap(ele, function () {
    setTimeout(function () {
       ele.style.display = 'none';
    }, 300); 
})

 

参考资料

手持设备点击响应速度,鼠标事件与touch事件的那些事

点击穿透

fastclick

彻底解决tap“点透”,提升移动端点击响应速度

也来说说touch事件与点击穿透问题

 

 

posted @ 2019-11-30 17:18  坤嬷嬷  阅读(960)  评论(0编辑  收藏  举报