一、事件捕获与冒泡
先扯一下事件的触发流程,这个之后会用到。
DOM2级事件规定事件包括三个阶段:
① 事件捕获阶段
② 处于目标阶段
③ 事件冒泡阶段
大概的流程就是事件从最外层一层一层往里面传递(捕获阶段), 到达触发事件的目标元素(目标阶段),然后再一层一层往上冒泡(冒泡阶段)。这个流程事件所经过的元素绑定的对应事件的侦听器都会被触发。例如对元素p的子元素c绑定点击事件,点击元素c后会先在p触发点击事件,使用dispatch触发也是。
PS:事件侦听通过target.addEventListener(type, listener[, useCapture])添加,useCapture参数代表是否在捕获阶段触发事件。
二、click事件的300ms延迟
移动设备中使用click事件和在PC端会有所不同,大多数基于触摸的浏览器设备,在点击时都会有个300ms的事件触发等待时间,原因在于单击后面还有个双击缩放动作,这个涉及到触摸设备的手势交互行为原生设计,设备需要通过时间判断是单击还是双击。
对于这300ms的延迟,根据网上搜索的资料,可通过几个简单的方法解决:
- 无视之。。。如果所涉及业务对点击的反应时间要求不大,300ms其实是可以忽略的。
- 在 chrome 和 firefox 的移动版本中,禁用页面缩放可消除该延迟。不过只兼容这两种不好。
- 在 chrome 32+ 中,设置 viewport 的宽度小于或等于物理设备的宽度。因为只支持chrome 32+,所以也忽略。
- IE10+中设置CSS触摸 action 属性。以后其他浏览器可能也会支持。但目前只支持IE10+,所以也忽略。
- 使用touch事件而不是click事件。这样必须可以解决延迟,但也因此引发了奇怪的问题,由此转入正文。
三、zepto的tap事件
如果我们直接使用touchend事件来代替click事件,一个很明显的不妥就是如果滑动到某个元素上放开手那么就会触发该元素的touchend事件,这明显不跟点击操作一致。另外一个问题即触发touchend事件后,在该区域300ms后还会触发一次click事件,如果一个元素touchend后消失,那么其点击区域底下的元素还会被触发一次click事件,这个问题叫做点透,不过这可以通过event.preventDefault来阻止(默认行为)。
为了更好的模拟点击事件并解决移动设备点击的300ms延迟,zepto的touch模块自定义了一个tap事件,该事件原理即是使用touch事件来模拟的。基本原理就是给元素绑定touchstart和touchend事件,然后判断前后事件的触发区域是否一致,是的话即触发自定义的点击事件。
看起来挺完美的,但事实上使用zepto的tap事件即使在事件回调函数中使用了preventDefault还是会出现点透问题。究其原因,就是zepto在触发事件的时候使用了定时器。在自定义事件被触发之前默认行为早就发生了,因此阻止不了click事件的触发。
暂时不管zepto,对于click事件,我们可以用另外一个插件提高click事件的响应速度,这就是fastclick!
四、fastclick
Fastclick的基本思想就是在某个父元素上捕获其子元素的touchstart与touchend事件,然后依然根据位置信息判断是否为点击动作,如果是的话立即使用dispatchEvent手动派发子元素的click事件,从而缩短了响应事件,并且在touchend的时候使用preventDefault阻止默认事件,防止300ms后再次触发click事件。
事情貌似就这么解决了,但事实就是没这么完美。撇开手动派发事件带来的性能损耗不说,在android上preventDefault阻止touchend触发click事件还没有用,即在android上只要使用了touchend,就一定会触发click事件,翻阅了很多资料后发现这个问题无法解决,有人说touchstart的时候调用preventDefault可阻止click,但亲测过不行。
这时候,就只能用比较猥琐的方法来解决了。在fastclick判断为点击操作并触发click事件时,我们给事件对象添加一个flag。从之前的内容可以得知click事件会先在外层容器上触发,因此我们在根目录上侦听click事件,当事件对象存在flag时即不做操作,当不存在时即阻止其事件派发流程。这样即元素绑定在元素上的点击事件侦听器只会被手动触发的click事件触发。
document.addEventListener(‘click‘, function (event) { if (event.myclick == true) { return true; } if (event.stopImmediatePropagation) { event.stopImmediatePropagation(); } else { event.propagationStopped = true; } event.stopPropagation(); event.preventDefault(); return true; }, true);
虽然使用fastclick可以解决click的延迟问题,但添加了许多额外的操作,性能上的损耗是无可避免的。因此如果对点击操作的要求不是很高的话还是建议忽略那点延迟。