问题的根源是几年前阿里巴巴的一次采访。过程中他说fastclick可以解决iPhone机300ms点击延迟的问题,随后被问及zepto的“点击穿透”现象,具体原因当时没有很好回答。主要原因是我没有深入研究原因。我只知道有这样的现象和问题,以及如何解决。面试一天后,我突然想起来了。我决定仔细研究一下。其实已经写了很多文章了,内容我就不重复了。我总结一下几点:300ms的延迟是浏览器需要判断是单机还是双击造成的。事件zepto的点击会“打穿”页面,因为它响应的是自己的点击(也就是触摸事件),并没有拦截原来的点击事件,导致事件重复执行了两次。当有遮罩层时会有“击穿”效果。如果你不太明白,请阅读这篇多年前关于zepto崩溃的文章。研究到这里的时候有一个很大的疑问:为什么mask层下的页面的click事件会在点击延迟后触发。我清楚地点击了。遮罩层的A按钮,为什么会执行下一页B按钮的事件。按照我最初的想法,应该是继续执行A键的事件!!!这时,我的心就是这样,于是开始探究这个问题。搜了大概的资料,基本没说具体原因。可能是我打开方式不对。反正我没找到。看看fastclick的源码,看看为什么没有这个问题,再看看sendClick的代码,心里突然有了一个猜测。FastClick.prototype.sendClick=function(targetElement,event){varclickEvent,touch;//在某些Android设备上,activeElement需要模糊,否则合成点击将无效(#24)}touch=event.changedTouches[0];//合成一个点击事件,带有一个额外的属性,这样它就可以被跟踪clickEvent=document.createEvent('MouseEvents');clickEvent.initMouseEvent(this.determineEventType(targetElement),true,true,window,1,touch.screenX,touch.screenY,touch.clientX,touch.clientY,false,false,false,false,0,null);点击事件.forwardedTouchEvent=true;targetElement.dispatchEvent(clickEvent);};注意这里的initMouseEvent。当时我在想,这一定和mouseEvent的执行原理有关。在这个阶段,我有一个线索。然后,我开始庆祝新年。过年享受生活,没碰代码和文档(感觉好堕落。。。),加上自己的跳槽和折腾,年后稍微稳定了一些,最近想起了一个猜想几年前我研究了一半,然后我开始继续研究它。顺便静下心来,这样才能进入状态。先说猜想——一开始在浏览器实际捕获click事件的时候,只有mouseEvent的相关属??性,也就是我们平时console.log(event)的一部分,会被浏览器组合生成用html和js我们常说的点击时间,然后触发我们使用js绑定的函数。基于这个猜想,我开始阅读mozilla和W3C的文档来了解mouseEvent。翻了翻文档,发现mouseEvent真的只有screenX,screenY,clientX,clientY,ctrlKey,altKey,shiftKey,metaKey,button,buttons,EventTarget?相关目标。其中,button和buttons是指鼠标的按键类型,即左键、右键和滚轮。用数字代替,0表示左键,1是滚轮,2是右键,其他更多的功能键都大于2。从上面我们可以看出,其实对于mouseEvent来说,它只知道我们在屏幕上的位置以及我们执行的操作(鼠标操作),但不知道它在哪个元素上。这就是fastclick最后还原用户点击事件的原因。clickEvent.initMouseEvent(this.determineEventType(targetElement),true,true,window,1,touch.screenX,touch.screenY,touch.clientX,touch.clientY,false,false,false,false,0,null);//detremineEvenType是fastclick封装返回的mouseEvent的type类型,即click或者mouseDown初始化一个鼠标事件,然后调度鼠标事件。浏览器自动响应后续处理。接下来看一下click的定义,如下图:click会多一个Event.target,而且必须是最顶层的事件目标。mozilla中的定义有些不同,多了currentTarget和type等。首先看一下EventTarget的定义:EventTarget是一个可以接收事件并可能有监听器的对象实现的接口。Element、document、window是最常见的事件目标,但其他对象也可以是事件目标,例如XMLHttpRequest、AudioNode、AudioContext等。从定义中可以看出,如果是点击事件,就必须有一个target来承载鼠标事件。一般来说,目标要么是一个元素,要么是一个文档,如果没有,那就是一个窗口对象。到这里大家应该比较明白了,这里就是浏览器的事件机制了。这应该是浏览器在initMouseEvent之后所做的事情,以查找是否有响应此事件的目标。如果没有目标响应这个事件,它最终会去到窗口。一般来说,我们不会在窗口上做事件。正在处理,不会有任何反应,事件结束。如果恰好此时有一个目标(一般来说是一个元素)响应,那么绑定的函数就会被执行。总结整个过程:用户点击屏幕,300ms内,浏览器拦截这个行为,并不会真正触发相关元素绑定的点击事件执行函数,而是记录操作相关数据,等待下一步操作,因为我们使用了zepto库来绑定点击事件。事件中触发touchend,立即执行相关操作,隐藏弹层。当300ms到来时,浏览器认为这个动作是click而不是dbclick,然后在同屏位置初始化一个mouseEvent,然后启动事件机制,发现在同一个位置有绑定点击处理函数的元素,执行这个函数,Over!!!这就是渗透发生的方式。PS:浏览器行为部分为猜测,未验证。至于解决办法:网上有很多,最好的是fastclick,但是fastclick也有其他问题,比如滑动时点击。另一种是使用zepto但preventDefault。Android自带的chrome已经解决了,可以用其他方法,官方文档,目前Safari也支持,不过是高版本上的。相关讨论请参考fastclick的issue
