click事件
click事件在移动端会有200-300ms的延迟,是因为手机上双击屏幕,缩放功能的存在。在手机上打开页面,快速双击时,页面会被放大。所以当你在点击第一次的时候,系统会等200-300毫秒,看你会不会点第二次,如果有第二次,则会放大当前页面内容;如果没有点第二次,才会执行点击事件。
同样的原因,dbclick事件在手机上也是失效的。
touch事件
touch相关的事件跟普通的其他dom事件一样使用,可以直接用addEventListener来监听和处理。touch事件中有一个事件对象 e。
最基本的touch事件包括4个事件:
- touchstart: 当在屏幕上按下手指时触发
- touchmove: 当在屏幕上移动手指时触发
- touchend: 当在屏幕上抬起手指时触发
- touchcancel 当一些更高级别的事件发生的时候(如电话接入或者弹出信息)会取消当前的touch操作,即触发touchcancel。一般会在touchcancel时做暂停游戏、存档等操作。
- 执行顺序:touchstart -> touchmove -> touchend -> click(200-300ms延迟)。
- 不同的设备上,touchmove触发的频率是不一样的。
- touchmove 事件会被多次触发,需要考虑事件节流(移动端的计算资源非常宝贵)。
- 当在指定区域绑定touchmove事件后,手指移出该区域且没有松开,事件依然会执行。
- touchend和touchstart事件的target是一样的,即使已经移出了该内容区。
- touchcancel 被别的应用打断时执行
TouchEvent
var box = document.querySelector('div')
box.addEventListener('touchstart', function (e) {
console.dir(e);
})
打印出事件对象 e,可以看到有以下内容:
TouchList
如果用户用3根手指触摸屏幕的,与之相关的每根手指都会生成一个touch对象,共有3个;
var box = document.querySelector('div')
box.addEventListener('touchend', function (e) {
for (let i = 0; i < e.changedTouches.length; i++) {
console.log(e.changedTouches.item(i))
}
}
因为是电脑模拟,只能有一个触点,所以e.changedTouches.length为1,打印结果如下:
tap事件
触碰事件,我目前还不知道它和touch的区别,一般用于代替click事件,有tap、longTap、singleTap、doubleTap四种类型。
- tap: 手指碰一下屏幕会触发
- longTap: 手指长按屏幕会触发
- singleTap: 手指碰一下屏幕会触发
- doubleTap: 手指双击屏幕会触发
swipe事件
滑动事件,有swipe、swipeLeft、swipeRight、swipeUp、swipeDown、五种类型。
- swipe:手指在屏幕上滑动时会触发
- swipeLeft:手指在屏幕上向左滑动时会触发
- swipeRight:手指在屏幕上向右滑动时会触发
- swipeUp:手指在屏幕上向上滑动时会触发
- swipeDown:手指在屏幕上向下滑动时会触发
移动端的点击穿透问题
一、click与300ms延迟
移动端浏览器提供了一个特殊的功能:双击放大。300ms的延迟就是来自于这里,用户碰触页面之后,需要等待一段时间来判断是不是双击(double tap)动作,而不是立即响应单击(click),等待的这段时间大约是300ms。
此外,使用原生touch事件也存在点击穿透的问题,因为click是在touch系列事件发生大约300ms才触发的,混用touch和click肯定会导致点透问题。
二、点击穿透问题
点击穿透现象有三种:
- 点击穿透问题:点击蒙层(mask)上的关闭按钮,蒙层消失后发现触发了按钮下面元素的click事件。蒙层关闭按钮绑定的是touch事件,而按钮下边元素绑定的是click事件,touch事件触发之后,蒙层消失了,300ms后这个点的click事件生效,event的target自然就是按钮下面的元素,因为按钮跟蒙层一起消失了。
- 跨页面点击穿透事件:如果按钮下面恰好是一个href属性的a标签,那么页面就会发生跳转,因为a标签跳转默认是click事件触发,所以原理和上面的完全相同。
- 另一种跨页面点击穿透问题:这次没有mask了,直接点击页面内按钮跳转至新页,然后发现新页面中对应位置元素的click事件被触发。和蒙层的道理一样,js控制页面跳转的逻辑是绑定在touch事件上的,而且新页面中对应位置的元素绑定的是click事件,而且页面在300ms内完成了跳转,三个条件同时满足,就出现这种情况了。
- 非要细分的话还有第四种,不过概率很低,就是新页面中对应位置元素恰好是a标签,然后就发生连续跳转了。。。诸如此类的,都是点击穿透问题。
三、解决方案
问题已经很明了了,有很多解决方案,但是思路不外乎2种:
- 不要混用touch和click,既然touch之后300ms会触发click,只用touch或者只用click自然不会存在问题。
- 吃掉或者消费掉touch之后的click,依旧用tap,只是在可能发生点击穿透的情形做额外的处理,拿个东西来挡住或者tap后延迟350ms在隐藏mask、pointer-events、在下面元素的事件处理器里做检测(配合全局flag)等等,能吃掉就行。
详细解决方案:
1、只用touch
最简单的解决方案,完美解决点击穿透事件。
把页面内所有click全部换成touch事件(touchstart、touchend、tap),需要特别注意a标签,a标签的href也是click,需要去掉换成js控制的跳转,或者直接改成span+tap控制跳转。如果要求不高,不在乎滑走或者滑进来触发事件的话,span+touchend就可以了。毕竟tap需要引入第三方库。
不用a标签其实没什么,移动app开发不用考虑SEO,即便用了a标签,一般也会去掉所有默认样式,不如直接用span。
2、只是用click
下下策,因为带来300ms延迟,页面内任何一个滴定仪监护都将增加300ms延迟,想想都慢。
3、拿个东西来挡住
比较笨的方法,不推荐用。
4、tap后延迟350ms再隐藏mask
改动最小,缺点是隐藏mask变慢了,350ms还是能感觉到慢的,只需要针对mask做处理就行,改动非常小,如果要求不高的话,用这个比较省力。
5、pointer-events
比较麻烦且有缺陷,不建议使用。
mask隐藏后,给按钮下面元素添加上pointer-events:none;样式,让click穿过去,350ms后去掉这个样式,恢复响应。缺陷是mask消失后的的350ms内,用户可以看到按钮下面的元素点着没反应,如果用户手速很快的话一定会发现。
6、在下面元素的事件处理器里做检测(配合全局flag)
比较麻烦, 不建议使用。
全局flag记录按钮点击的位置(坐标点),在下面元素的事件处理器里判断event的坐标点,如果相同则是那个可恶的click,拒绝响应。上面说的只是想法,没测试过,实在不行就用记录时间戳判断,等待350ms,这样就和 pointer-events 差不多。
7、fastclick
首先引入fastclick库,再把页面内所有touch事件都换成click,其实稍微有点麻烦,建议引入这几KB就为了解决点透问题不值得,不如用第一种方法呢。