一、前置知识
1.1 click事件和touch事件区别
(1) pc端/移动端的click事件
pc端的click点击事件(鼠标交互事件),
定义是鼠标的按钮被摁下,并在300ms内释放,则可以认为是一次单击事件。
移动端的click点击事件(触摸交互事件),
定义是手指触摸到屏幕开始,并在300ms内离开屏幕且这段时间内手指不能移动,此时可以认为发生了一次移动端的点击事件。
(2) 移动端的touch事件
touch事件的4种分类
- touchstart:当手指与屏幕接触时触发。
- touchmove(0 次或多次):当手指在屏幕上滑动时连续地触发。
- touchend:当手指从屏幕上离开时触发。
- touchcancel:当touch事件被迫终止,例如电话接入或者弹出信息时会触发,或者当触摸点太多,超过了支持的上限(自动取消早先的触摸点)时触发,一般不常用。
touch和click的执行顺序:touchstart-> touchmove->touchend <==> 等价于click事件
问题1:如何只触发touch事件,但是不触发click事件?
答:使用preventDefault()方法阻止冒泡
二、移动端相关问题
2.1 IOS点击300ms延迟问题
(1)问题概述
这要追溯至2007年初,苹果公司在发布首款iPhone前遇到了一个问题:当时的网站都是为大屏幕设备所设计的,于是提出了视区(Viewport)的概念,其中一项即是用户在浏览网页时,可以在页面的任何地方通过双击操作将页面放大(Double Tap to Zoom)。这个交互功能提升了用户浏览网页时的体验,于是Android和iOS的移动端浏览器纷纷支持了这个功能,但是对于双击这个操作而言,其实是包括了两次单击操作,当第一次单击完成后,系统需要有一段时间来监听是否有第二次单击,如果有则表明此次操作是一个双击操作,而这段时间间隔大概有300毫秒(ms)。
因此,哪怕是只想要单击这个事件,也都会经过双击放大这个判断逻辑,导致要等到300毫秒之后才能收到单击事件程序逻辑的反馈,这就是300毫秒的单击延迟问题。
简单来说,就是webview为了实现双击放大操作,因此需要区分出用户的点击操作是:单击 or 双击,而判断的逻辑是"两次点击操作之间的间隔 > 300ms"。因此即使是每次单击完成之后,系统需要有一个300ms延迟,从而才能判定本次点击是单击事件。
问题: 为什么安卓没有300ms延迟问题?
因为Android系统的浏览器,可以通过给视区设置user-scalable=no来禁止用户进行缩放,这样就没有双击放大功能,系统就关闭了单击/双击的区分逻辑,随后就可以正常地使用原生的click事件而没有延迟;
对于iOS系统而言,浏览器对user-scalable支持度存在Bug(漏洞),导致了无法通过简单的设置来达到正常使用原生click事件的目的。
(2)如何在ios端解决300ms点击延迟问题
既然ios系统中始终绕不开单击/双击click的判断逻辑,那么就使用touch事件(touchstart、touchmove和touchend事件来模拟一次单击操作,所以,在iOS移动端,如果想要实现真正的单击事件而没有300毫秒延迟问题,就不能采用原生的click事件,可以通过touch(touchstart、touchmove和touchend)事件来模拟一次单击操作。好在当前业界已有比较流行的方案,例如Zepto.js中的tap事件和FastClick.js库可用来解决这个问题,在这里主要介绍一下FastClick.js库。
FastClick.js是FT Labs团队结合touch事件专门为解决移动端浏览器的300毫秒单击延迟问题所开发的一个轻量级的库。正常情况下,在移动Web端,当用户单击屏幕时,会依次触发touchstart、touchmove(0 次或多次)、touchend、click(原生)这些事件。touchmove事件只有当手指在屏幕上移动时才会触发。Touchstart、touchmove或者touchend 事件的任意一个调用event.preventDefault()方法,都会直接阻止原生click事件的触发。
FastClick的实现原理是在检测到touchend事件触发时:
1.通过stopImmediatePropogation,把浏览器在300毫秒之后原生的click事件阻止掉;
2.通过dom获取到原有的oldClick绑定的事件函数放到addEventListener中,然后增加 addEventListener中的click事件;(注意这里是webview可以获取dom,如果是在小程序中,则不能用该方法,因为小程序不能获取dom)
3.立即通过dispatchEvent立即触发这个模拟的click事件,这样就消除了300毫秒的延迟,提供了一个快速响应的“单击”事件。
2.2 “点击穿透”问题
场景描述
在移动Web端,有一个很常见的应用场景,单击一个按钮会出现一个蒙层,此蒙层是全屏遮盖,并且有最高层级,当单击蒙层时,蒙层消失。此场景和交互操作看似并没有什么问题,但是假如页面中有一个绑定了单击事件的<div>元素被蒙层遮盖,而单击蒙层关闭时的位置刚好和该<div>元素重合,那么蒙层关闭后会同时触发该<div>元素的单击事件,对于用户来说,这个操作并不是要单击该<div>元素,这就是所谓的“单击穿透”问题,如图。
出现“单击穿透”问题需要有个条件,即蒙层是通过绑定的touch事件来实现隐藏,而其遮盖的<div>元素绑定的是原生click事件,这样就形成了touch事件触发之后,蒙层隐藏了,300毫秒后当前这个触摸点的click事件又触发了,就形成“单击穿透”。
点击穿透原因
移动Web端的“单击穿透”问题出现的原因其实和300毫秒单击延迟问题脱不了关系:touchend后弹出层和遮罩就被隐藏了继续等待300ms发现没有其他行为了,则继续触发click,由于这时弹出层已经消失,而下层的click也会继续执行捕获冒泡,所以当前click事件的target监听了click事件就执行了。整个事件触发过程为 touchend -> tap -> click。
解决“单击穿透”问题可以从问题出现的原因上来着手,主要有以下两种解决方案:
- 不要同时混用touch事件和click事件,要么给蒙层和
<div>元素同时绑定touch事件,要么同时绑定click事件,在iOS 9.3版本之后,只用click事件即可,此方案体验最好。 - 延迟蒙层消失的时间,例如在touch事件触发后,在350毫秒后再让蒙层消失,这样后面的
<div>元素就不会触发click事件了,此方案会导致蒙层消失的响应慢,体验差,并且有时会触发两次消失逻辑,故不推荐使用。
无论是300毫秒单击延迟问题,还是“单击穿透”问题,这些都是移动Web端特有的问题,也在一定程度上反映出移动Web端环境的复杂性,需要注意支持度和兼容性问题的地方很多,所以大家在进行移动Web端开发时,要有意识地去关注这些问题。
2.3 vant-search组件中clear在pc端失效
我们通常使用的移动端UI组件库是vant,而其中的search搜索组件中的clear清空事件绑定的是touch事件,而pc端没有touch事件,那么就导致在转化到pc端的时候,pc点击清空event对应的@clear不响应(clear 的触发时机是 touchstart)。而对应的PC && 移动 兼容方案如下:
// 统一都是用click事件触发
<van-search
v-model="searchValue"
:clearable="false"
>
<template #right-icon>
<van-icon name="cross" @click="()=>onClear()" v-if="searchValue"/>
</template>
</van-search>
参考文章: juejin.cn/post/713824…