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

前言

作过移动端H5页面的同窗确定知道,移动端web的事件模型不一样于PC页面的事件。看了一些关于touch事件的文章,我想再来回顾下touch事件的原理,为何经过touch能够触发click事件,touch事件是否是万能的以及它可能存在的问题。css

touch事件的来源

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

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

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

19161138-7c39b72bc6c048738962c042d1df766f.png

能够看到在手机上,当咱们手触碰屏幕时,要过300ms左右才会触发mousedown事件,因此click事件在手机上看起来就像慢半拍同样。web

touch事件中能够获取如下参数

参数 含义
touches 屏幕中每根手指信息列表
targetTouches 和touches相似,把同一节点的手指信息过滤掉
changedTouches 响应当前事件的每根手指的信息列表

tap是怎么来的

用过Zepto或KISSY等移动端js库的人确定对tap事件不陌生,咱们作PC页面时绑定click,相应地手机页面就绑定tap。但原生的touch事件自己是没有tap的,js库里提供的tap事件都是模拟出来的。chrome

咱们在上面看到,手机上响应 click 事件会有300ms的延迟,那么这300ms究竟是干吗了?浏览器在 touchend 后会等待约300ms,缘由是判断用户是否有双击(double tap)行为。若是没有 tap 行为,则触发 click 事件,而双击过程当中就不适合触发 click 事件了。由此能够看出 click 事件触发表明一轮触摸事件的结束。浏览器

既然说tap事件是模拟出来的,咱们能够看下Zepto对 singleTap 事件的处理。见源码 136-143 行,能够看出在 touchend 响应 250ms 无操做后,则触发singleTap。移动端web

点击穿透的场景

有了以上的基础,咱们就能够理解为何会出现点击穿透现象了。咱们常常会看到“弹窗/浮层”这种东西,我作个了个demo。ide

20151004_01.jpg

整个容器里有一个底层元素的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“穿透”到下层去了。

完整demo请用chrome手机模拟器查看,或直接扫描二维码在手机上查看。

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

穿透的解决办法

1. 遮挡

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

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

2. pointer-events

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

取值 含义
auto 效果和没有定义 pointer-events 属性相同,鼠标不会穿透当前层。
none 元素再也不是鼠标事件的目标,鼠标再也不监听当前层而去监听下面的层中的元素。可是若是它的子元素设置了pointer-events为其它值,好比auto,鼠标仍是会监听这个子元素的。

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

所以解决“穿透”的办法就很简单,demo以下

$('#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库,其实现思路是,取消 click 事件(参看源码 164-173 行),用 touchend 模拟快速点击行为(参看源码 521-610 行)。

FastClick.attach(document.body);

今后全部点击事件都使用click,不会出现“穿透”的问题,而且没有300ms的延迟。解决穿透的demo

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

参考资料

本文最先发表在个人我的博客上,转载请保留出处 http://jsorz.cn/blog/2015/10/touch-event-and-defect.html

相关文章
相关标签/搜索