背景
- 部分移动设备(包括iPhone)在手指离开屏幕后的
300ms
,才会找到用户点击的位置,再去触发对应位置最上层元素的click(这个延迟是因为移动设备要确定用户这次触摸屏幕是一次单击还是双击,如果是双击,就会将页面放大) - 这300ms在用户感觉会觉得页面响应慢不流畅,许多开发者追求用户体验,使用各种方法让click事件在用户手指一离开页面就触发
目前能加快响应的主流两个库有 zepto的touch模块 & fastclick
zepto
- 解决方案
新增自定义tap事件,在document.body上监听touchstart,在touchend后触发目标元素所绑定的tap事件,以达到快速响应的目的。 - 带来的问题
zepto对click事件没有多做处理。研究zepto的源码发现,tap的实现是通过在body上的touch事件来完成模拟,当有touch事件冒泡到body时,touchstart到touchend手指没有在屏幕上移动,就认为是一次tap,然后找到目标元素触发它绑定的tap事件。所以没办法对目标元素的touch事件执行preventDefault来阻止默认的click事件发生(touch事件执行prevenDefault阻止click触发 这一方法,在一些浏览器和html容器中并不完全支持,ios是完全支持的),除非对每个绑定tap的dom都绑定一个touch事件,阻止默认click的发生,这样做成本有点大,而且容易被用户覆盖掉。所以zepto在触发了tap事件后300ms会继续触发click。这样的话如果一旦被tap的元素在300ms内消失(tap事件中令这个元素立马隐藏或者删除了),原点击位置最上层的元素就会触发的click事件。
所以最好一个网站中tap和click不要混用,用了tap就统一使用tap。看起来好像只要页面统一使用tap事件,click即便触发,也没有对应函数执行,那就不会有问题了。实际上并不是。如果这个位置的元素是有浏览器自带的click默认函数呢?比如是一个input text框,那么就会跳出软键盘;或者一个a标签,那么就会直接页面跳转。这就是所谓的“点透”,推荐看这篇文章zepto tap “点透”研究
fastclick
- 解决方案
github源码注释很全面,容易看懂。
fastclick重写了click事件,覆盖了原生的click事件,并删除了mousedown、mouseup事件,感觉目的是为了阻止300ms后的click触发。fastclick还用了一些方法:如在touch事件上preventDefault()、重写的click事件上stopImmediatePropagation()去阻止第二次click的发生。在不刷新页面的情况下,确实挺有效的。 - 待解决的问题
如果当前点击刷新了页面,js刷新状态重新执行,300ms后的click还是有可能会在刚刷新的页面上触发(某些机型,如三星S3、红米。在iOS上,在touchend事件中使用event.preventDefault()可以有效阻止第二次click,即使刷新页面也有效)。
结论
- 不是迫不得已,最好还是不要使用快速响应,300ms也没有那么难以忍受
- 如果使用了前端框架,页面之间的跳转都使用路由实现而没有页面刷新,那么请你放心的使用fastclick。
- 如果网站内有页面刷新,你又用了fastclick(而且你还需要坑爹的兼容各种安卓机),那么在每个会刷新页面的点击位置,都要检查页面刷新后,在屏幕点击位置上的元素,是不是有click事件会执行,如果有,要么移动一下位置,要么在html中就要禁止它的默认click事件(如a标签href="javascript:void(0)"),因为js加载再执行还需要一定时间,不一定能来得及阻止它的默认click事件。