Touch穿透
一、前言
相信很多移动开发者都有这样一个体会,那就是我们在移动端点击事件click对比touchend会有延迟,这是为什么呢?
其实这是因为览器在click后会等待约300ms去判断用户是否有双击行为,手机需要知道用户是不是想双击放大网页内容。如果300ms内没有再一次click,那么就判定这是一次单击行为,所以我们基本上都用(touchstart/touchend),但是这些事件在执行完之后还会执行一次click事件。(至于具体的原因这要从JS事件监听机制的根本的讲起,解释起来太麻烦,感兴趣的同学可以动手了解一下,我们这里就不做过多说明了)。
二、touch事件的来源
PC网页上的大部分操作都是用鼠标的,即响应的是鼠标事件,包括mousedown、mouseup、mousemove和click事件。一次点击行为,事件的触发过程为:mousedown -> mouseup -> click 三步。
因为手机上没有鼠标,所以就用触摸事件touch去实现类似的功能。touch事件包含touchstart、touchmove、touchend,注意手机上并没有tap事件。手指触发触摸事件的过程为:touchstart -> touchmove -> touchend。
手机上没有鼠标,但不代表手机不能响应mouse事件,其实是借助touch去触发mouse事件。有人在PC和手机上对事件做了对比实验,以说明手机对touch事件相应速度快于mouse事件。
从上面的图表对比中我们可以看出在手机上,当我们手触碰屏幕时,要过300ms左右才会触发mousedown事件,所以click事件在手机上看起来就像慢半拍一样。
三、点击穿透的场景
点击穿透的现象主要分为四种:
1.点击蒙层(Mask Layer)上的关闭按钮,蒙层消失后发现触发了按钮下面元素的click事件。
蒙层的关闭按钮绑定的是touch事件,而按钮下面元素绑定的是click事件,touch事件触发之后,蒙层消失了,300ms后这个点的click事件出发,事件的目标元素自然就变成了按钮下面的元素,因为按钮跟蒙层一起消失了。
2.如果按钮下面恰好是一个有href属性的a标签,那么页面就会发生跳转,因为a标签跳转默认是click事件触发,所以穿透原理和上面的完全相同。
3.如果按钮下面恰好是文本框input或文本域textarea,则文本框或文本域就会获取焦点,穿透原理和上面的相同。
4.这次没有蒙层,直接点击页内按钮跳转至新页,然后发现新页面中对应位置元素的click事件被触发了。
和蒙层的道理一样,js控制页面跳转的逻辑如果是绑定在touch事件上的,而且新页面中对应位置的元素绑定的是click事件,而且页面在300ms内完成了跳转,三个条件同时满足,就出现这种情况了。
其他的点击穿透的例子还有很多,我就不一一细说了。
四、Touch穿透的解决办法
1.延迟
蒙层被点击后延时至少300ms再在彻底隐藏掉蒙层显示下层的内容,缺点是隐藏蒙层变慢了,350ms还是能感觉到慢的,但是这种方法只需要针对蒙层做处理就行了,改动非常小,如果要求不高的话,用这个比较省时省力。
2.增加中间蒙层
我们还可以动态地在触摸位置生成一个透明的元素,这样当上层元素消失而延迟的click来到时,它点击到的是那个透明的元素,就不会“穿透”到下面去了,然后再在一定的延迟后将生成的透明元素移除。
3.利用pointer-events方法
pointer-events是CSS3中的属性,它有很多取值,auto | none | visiblepainted | visiblefill | visiblestroke | visible | painted | fill | stroke | all,有用的主要是auto和none,其他属性值为SVG服务。
当pointer-events取值为auto时,效果与pointer-events属性未指定时的表现效果相同;当取值为none时,元素永远不会成为鼠标事件的目标,但是,当其后代元素的pointer-events属性指定其他值时,鼠标事件可以指向后代元素,在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器。
<div class="upbox"></div> <div class="underbox"></div> <div class="button"></div> $('.button').on('touchstart',function(){ $('.upbox').hide(); $('.underbox').hide(); //马上让它不能点击 $('.underbox').css('pointer-events','none'); //因为click事件需要300ms响应,所以我们时间定义360ms,时间一过又可以正常点击了 setTimeout(function(){$('.underbox').css('pointer-events','auto')},360); });
4.只用click
页面里的点击事件都用click来触发,但是这样的话,页面里的点击交互都将增加300ms延迟,想想都慢,但是如果交互性要求不高的话可以这么做,强烈不推荐 ,快一点总是好的。
5.使用fastclick插件
如果不介意多加载几KB的话,可以使用fastclick库,其实现思路是:取消 click 事件,用 touchend 模拟快速点击行为,从此所有点击事件都使用click,不会出现“穿透”的问题,并且没有300ms的延迟。不建议使用,因为有人遇到了bug, fastclick 导致click事件触发两次的问题,而且开发者还必须先引入fastclick库,再把页面内所有touch事件都换成click,稍微有点麻烦,而且多引入几KB的文件只是为了解决点透问题不值当,不如用前三种方法中的任一种。
五、总结
除了上面这五种办法外,相信还有其他我本文中未收录的方法,这就需要大家一起来探索了,其实遇到问题,第一重要的不是立即着手解决问题,而是找到问题的根源所在,之后针对根源去消灭问题,至于解决问题的方法,一千个人眼中一千个哈姆雷特,你喜欢哪种,就用那种方法来。
此文章主要发布在网站H5案例分享、公众号H5握手和个人博客中,转载请注明出处。