click 延迟

延迟原因

谷歌的开发者文档《300ms tap delay, gone away》如是说:

For many years, mobile browsers applied a 300-350ms delay between touchend and click while they waited to see if this was going to be a double-tap or not, since double-tap was a gesture to zoom into text.

最早的 iPhone 的 Safari 浏览器中,为了实现触屏中双击放大的效果,当用户点击屏幕后会判断在 300ms 内是否有第二次点击,如果有,就理解成双击,若没有就是单击,就会触发click事件。

当你点击移动设备的屏幕时,可以分解成多个事件,这些事件都是按顺序依次触发的。

touchstart --> touchmove --> touchend --> click

去除延迟的方法

FastClick doesn't attach any listeners on desktop browsers.

Chrome 32+ on Android with width=device-width in the viewport meta tag doesn't have a 300ms delay, therefore listeners aren't attached.

<meta name="viewport" content="width=device-width, initial-scale=1"> Same goes for Chrome on Android (all versions) with user-scalable=no in the viewport meta tag. But be aware that user-scalable=no also disables pinch zooming, which may be an accessibility concern.

For IE11+, you can use touch-action: manipulation; to disable double-tap-to-zoom on certain elements (like links and buttons). For IE10 use -ms-touch-action: manipulation.

from: FastClick - When it isn't neededopen in new window

tap 事件

实现原理

有些第三方库如zeptofastclick等实现了tap事件,用于替代移动端的click事件,解决点击延迟的问题。

zeptofastclick都是在touchend触发之后立即触发事件,不同的是:

  • zepto:手动触发自定义的tap事件
  • fastclicktouchend事件触发后,使用document.createEvent手动生成click事件并触发,再取消浏览器触发的click事件

这里有一个关键的问题是,不能每次touchend之后都触发tap事件,因为有可能用户是在上下滑动而不是在点击(否则可以直接通过监听touchstart事件就可以了)。

因此,如何判定用户是在点击还是在上下滑动呢?

  • zepto:通过判断位移偏差,即记录下touchstart时的初始位移,用touchend时的位移减掉初始位移的偏差,如果这个差值在30以内,则认为用户是点击,否则认为是滑动。
  • fastclick:通过判断时间偏差,分别记录touchstarttouchend的时间戳,如果它们的时间差大于700毫秒,则认为是滑动操作,否则是点击操作。

如何像jQuery/Zepto一样,实现一个简单的tap事件?请参考:前端早读课-【第1005期】从移动端click到摇一摇open in new window

zepto 的 tap 事件点击穿透问题

现象

遮罩层中有一标签绑定了tap事件,触发时遮罩层消失,该标签正下方有以下的元素之一:

  • 绑定了click事件的元素、click时会触发事件(focus/focusout)的元素
  • 点击时有默认行为的元素,如超链接a
  • input(会出系统键盘的type类型)

此时点击上层的标签,同时也会触发下层元素的click事件,出现穿透的现象。

原理

当触发tap事件,上层遮罩层关闭后,此时事件只进行到touchend事件,而大概 300ms 后才触发click事件,当click事件触发时,上面的遮罩层已消失,就相当于点击到了下层的元素。

解决方案

  • 引入fastclick.js
    • 存在的问题:
  • touchend代替tap事件并阻止掉touchend的默认行为e.preventDefault()
    • 存在的问题:在很远的区域滑动到目标元素,然后松开,会触发touchend事件
  • click事件代替tap事件或者使用tap事件并且添加延迟一定时间(300ms+)来处理事件
    • 存在的问题:需要延时处理事件回调
$("#cbFinish").on("tap", function (event) {
    setTimeout(function(){
        //很多处理比如隐藏什么的
    },320);
});
1
2
3
4
5

zepto 为何不使用 e.preventDefault() 来解决穿透问题?

因为zeptotap事件统一是在documenttouchend时触发的,若在这里使用e.preventDefault(),那页面上所有元素在touchend后触发的事件都不会被执行了。

Reference: