PGYou-bbt | 2021年,站在现代浏览器上再谈谈移动端点击延迟问题

2021年,站在现代浏览器上再谈谈移动端点击延迟问题

2021-9-24

一、老生常谈的移动端点击延迟问题

为什么存在 300ms

在移动端 web 网页,从点击到实际触发 click 事件存在 300ms 左右间隔的问题由来已久。事实上,这 300ms 在诞生之初,作为一个灵光一闪的创意,优雅地解决了当时移动端网页的一些痛点问题。

时间追溯到首代 iPhone 发布的年代,当时绝大多数的网站都是为 pc 端这样的大屏幕设计的。对当时刚刚诞生的许多移动端设备来说,使用这样的网站在交互和阅读体验上是一种灾难。

活人不能被尿憋死。苹果的工程师在移动端上设计了一些独有的交互,弥补这些问题。与 300ms 点击延迟最为相关的,就是双击缩放。

🌰 举个例子:阅读网站上的某一篇文章时,如果该网站未适配移动端,会发现正文区域并未占满屏幕,非关键因素占据了不少空间。通过快速双击屏幕,safari 会自动的识别双位置的元素,并放大到适合屏幕尺寸的大小。

img双击正文区域=>img

这个交互设计得十分优秀。相比双指拖动放大而言,除了操作更便捷,双击放大中还隐含了点击位置,即放大的对象。浏览器可以识别这个对象,放大到最合适的尺寸。为了区分用户单击双击,Safari 不得不等待 300ms,如果 300ms 内用户未再次点击,则认定为单击,否则就会认定为双击。

最开始 iPhone Safari 采用了这个方案,后来安卓等其他系统的浏览器纷纷跟进,采用了这个方案。所以在 2012 年之前,在移动端存在 300ms 点击延迟几乎是一个再正常不过得事。(PS: 除了双击缩放,Safari 还有一个独有的交互: 双击屏幕靠下或靠上的区域,会滚动下屏幕)

300ms 开始成为问题

使用等待 300ms 区分双击,实现双击缩放的这一个方案的背景是,当时的移动设备性能有限,本身的反应速度就没那么快;当时移动设备的屏幕多为电阻屏,在触控上灵敏度有限,所以 300ms 成可以接受的一个很小的时间间隔。

但是 2012 年前后,移动端设备的主要代表——手机,开始了进化。300ms 越来越成为一个不可接受的延迟。网站也针对移动端设备进行适配,双击缩放带来的优势已经渐渐地小于 300ms 延迟带来的劣势。300ms 延迟成为一个如芒在背、如鲠在喉的问题。

二、一些解决方案和最佳姿势

从交互困境,到引入双击方案导致出现 300ms 延迟,再到各个厂商出台解决方案、第三方解决方案,中间存在历史原因和当时的条件限制。只有结合当时立场,才能解释为什么有这么多方案。下面讲述各个方案时,都会尽量从当时的视角进行介绍。

1、设置 meta 标签,禁用缩放。

2012 年后,设备更新,响应速度变快,300ms 延迟反而成为提高可用性上的绊脚石。2013 年之初,Chrome 和 Firfox 先后推出了补救措施,既然是为了实现双击缩放引入了 300ms,那么如果网站声明不需要双击缩放功能时,可以通过禁用双击来取消 300ms 延迟。开发者通过在 html 的 head 标签中添加如下声明,完全禁用双击缩放功能。

<meta
    name="viewport"
    content="minimum-scale=1.0,maximum-scale=1.0,initial-scale=1.0,user-scalable=no"
/>

虽然通过完全禁用缩放功能,可以取消 300ms 延迟,但是完全禁用缩放也带来了一些限制。就算网站进行针对移动端的布局适配,如果用户依然想对某个局部的文字或者图片之类的进行放大查看,就变成了一个做不到的事。而且此时电容屏已经成为移动设备主流屏幕了,多点触控可以引入另一个交互,双指缩放。完全禁用缩放,依然在可用性上存在不小的影响。

一计不成又生一计。当时很多网站已经做了响应式设计,而在网站进行移动端适配中,免不了要进行如下声明。

<meta
    name="viewport"
    content="width=device-width"
/>

通过这个声明,也会禁用双击缩放,从而取消 300ms 的延迟。但是,这个属性乍一看是解决视口宽度和设备实际宽度之间问题的,为什么会禁用双击缩放呢?这种方式和上一种方式又有什么不同呢?

首先,由于很多网站已经做了响应式设计,或者针对移动端设备进行的单独的适配,需要通过双击缩放提高可读性的前提不存在了,或者说需要频繁地、精准地将某个元素,缩放到适合屏幕大小的需求,不存在了。当然并不是所有的网站都不需要双击缩放,那么区分两者的因素就是是否进行可移动端适配,也就是:是否存在 width=device-width 的声明。同样是安卓阵营的浏览器厂商首先跟进,对于存在 width=device-width 声明的网站,禁用双击缩放功能,无需已经进行了移动端适配的开发者进行冗余的,重复的其他声明,直接消灭 300ms 延迟,解决了移动端使用的卡顿感问题。

其次,和直接声明禁用缩放不同。 width=device-width 声明,禁用但是不完全禁用缩放功能,用户依然可以通过双指操作进行缩放。在 2013 年,这个功能已经在 Chrome 和 Firefox 上实现了(chrome 取消 300ms),然而 Safari 直到 2016 年才正式实现对声明 width=device-width 的网站禁用双击缩放(webkit 内核支持 width=device-width 声明) 。

优势 🌝 🌝 🌝局限 🌚 🌚 🌚
修改成本小。并非所有浏览器都支持 width=device-width 禁用双击缩放。在早期,还是很多野鸡浏览器不支持。Safari 直到 2016 年才支持。早期 IE 不支持。

2、兼容 IE(Trident 内核),设置 css 样式,禁用缩放。

早期 IE 不支持使用 meta 标签禁用双击缩放逻辑,但可以采用 CSS—— "touch-action" 属性,通过设置属性值为“none” 或者 "manipulation"。

CSS 属性 touch-action 用于设置触摸屏用户如何操纵元素的区域(例如,浏览器内置的缩放功能)。 ——MDN

关于 "touch-action" 属性的详细用法,可以查看MDN,不再过多赘述。简而言之,也是通过禁用双击缩放逻辑(也会禁用双指缩放),达到无需采用 300ms 延迟的逻辑。

优势 🌝 🌝 🌝局限 🌚 🌚 🌚
修改成本小。兼容 IEtouch-action 属性禁用双击会带来副作用,比如设置一个元素为 touch-action:none,会禁用元素(及其不可滚动的后代)上的所有手势。例如双指缩放也被禁用了。最早和指针事件一起由微软提出并实现,后面正式成为 W3C 标准,早期不是所有浏览器都支持。

3、引入第三方库。

以 FastClick 为代表,尝试使用其他事件类型,例如 touchend,规避 click 事件的延迟。FastClickjs 给出的是一个比较完备的解决方案,并且视图减少对业务代码的入侵,让业务代码照常监听 click 事件,像一个 ployfill 一样,抹平平台差异。

其原理简单来说就是监听根元素的 touchend 事件,并且立马生成一个自定义的 click 事件,通过 eventTarget.dispatchEvent 派发,模拟触发 click 事件。在 mouse 事件中,通过 preventDefault 、stopImmediatePropagation 和 stopPropagation 取消接下来的 click 事件。

img

使用 fastclickjs,对原生事件流程存在一定入侵,存在不可知风险。例如依赖的其他库(UI 组件库等)可能会和 fastclick 有冲突。

优势 🌝 🌝 🌝局限 🌚 🌚 🌚
无需修改业务逻辑代码。基本无需考虑浏览兼容性对原生事件模型有入侵性,存在不可知风险可能和别的库存在冲突,例如 https://github.com/ant-design/ant-design/issues/27774额外依赖,导致项目膨胀

4、不使用 click 事件,而是使用移动端专属的 touch 事件,或者拥抱未来的 pointer 事件

如果不想引入第三方库,而是希望从代码层级规避移动端点击延迟。可以不使用 click 事件,使用 touch 事件或者,更新的 pointer 指针事件(可能存在一些兼容性问题)。

对于纯移动端项目,大可以全部使用 touch 事件,可以避免重复触发和一些场景的点击穿透问题。但还是需要注意,某些 html 标签,原生行为就是由 click 事件触发,例如 a 标签等。

对于同时兼容 PC 端和移动端的项目,不可避免的会出现 click 事件和 touch 事件混合使用的场景。可以在局部,针对局部场景实现类似 fastclick 逻辑的补丁。

优势 🌝 🌝 🌝局限 🌚 🌚 🌚
无需引入额外依赖不依赖浏览器特性,无需考虑浏览兼容性成本高,对业务代码入侵性高,影响全部处理事件交互部分的代码不是一劳永逸的解决方案

总结

对于纯移动端项目,完全可以从开发初始,就规定使用和移动端更贴合的 touch 事件。但是对于已有项目和兼容 PC 端移动端项目,通过设置 meta 标签/touch-action 禁用缩放,是一个更好的选项。而且在前端全面拥抱现代浏览器的今天,无需考虑历史因素中的兼容性问题。

三、如幽灵般再次出现的点击延迟和罪魁祸首

困境

docs 在线文档,同时兼容移动端 PC 端,大多数交互事件沿用 PC 端事件。在 iOS 移动端访问,沿用 click 事件,点击存在 300ms 延迟。在此之前,曾经尝试过 iOS 点击延迟常用解决方案,即在移动端通过 meta 标签声明禁用双击放大,无果。docs 文档存在不可知、非常规原因,导致 click 事件触发延迟。

对引起 300ms 延迟的来龙去脉进行了深入检索。尝试所有常规解决方案,无果。向非常规方向思路转变:

1、浏览器 bug,触发 300ms 延迟原因可能是任意一个不相干的代码,触发原因和结果中间无表层逻辑联系。

2、隐秘的浏览器”特性“,iOS-WKWebview 为了解决某个问题,特意为之的逻辑,触发延迟的原因和结果之间一定存在联系。

方向 1 属于死路,无法进行深究。所以从方向 2 入手,结合 300ms 延迟来龙去脉,点击延迟是为了实现双击逻辑,延迟和某些事件的触发有关。按此思路进行排查。

原因

经过对 docs 文档运行时所有注册事件进行排查,最后锁定了两个 mouse 类事件。在一个 mousemove 的事件处理函数中,存在异步代码:

setTimeout(() => {
 // ...
}, 300)

正是这个巧合的 300ms 定时器,误导了所有人,认为点击延迟来源于传统的双击缩放。

经过在 iOS 中测试,下面几个疑问进行了验证:

  • mousemove 事件处理函数,会阻塞后触发的 mpusedown 和 mouseup 吗?

会阻塞所有后续触发的事件,包括 mousedown、mouseup、click。

  • 其他事件处理函数有类似的现象吗?

测试了 touch 类,mouse 类事件,只发现 mousemove 有这个现象。

  • 如果 setTimeout 的时间特别久,会一直阻塞吗?

在定时器时间小于 400ms 时,会等待异步代码执行完毕。大于 400ms 完全不阻塞,完全不阻塞的意思是,不会等待 400ms,而是同步代码执行完毕后,正常触发后续事件。

  • 使用 setInterval 阻塞吗?如果 setTimeout 中,嵌套地使用 setTimeout,会一直阻塞吗?

经测试,使用 setInterval,完全正常,不阻塞后续事件触发。在 setTimeout 中嵌套使用的时候,第二层异步延迟会被忽略。

  • 有无官方特性介绍文档?

很遗憾在官方和非官方的平台都没有找到类似的问题描述和解释。但经过测试,并非某个 iOS 版本或者机型存在这个特性,在多台机型和多个系统版本中测试都会复现。应该是 wkWebview 的特性。

最终结论是:

iOS h5 场景下,注册 mousemove 事件监听,并且使用 setTimeout 注册一个时间不长于 400ms 的异步任务(setInterval 不受影响),浏览器会等待响应 mousemove 的事件代码完全执行完成,然后才触发后续事件(包括后续触发的 mousedown、mouseup、click)。

拓展思考

1、 在移动端-PC 端共用一套核心代码的情况下,如何设计架构分层或者怎样的代码组织方式,能隔离 pc 端、移动端,实现不加载多余的代码、样式,避免类似点击穿透、异步逻辑延迟 click 事件问题。

2、针对这个问题,能否从工程层面上开发检查工具,识别是否有不应该注册的事件监听,识别是否存在异步代码。

Copyright ©PGYou 2022