前端++Web前端之路前端开发那些事

移动端 click 事件 300ms 延迟的前世今生

2017-03-28  本文已影响186人  白蜀黍

原文首发于 baishusama.github.io,欢迎围观~

存疑

最开始,我遇到的其实是“移动端遮罩层滑动穿透”的问题。

在查找“滑动穿透”问题相关资料的时候,我搜到了很多 click 300ms 延迟的问题。我那个时候有些不知所云,因为我自己并没有真实遇到过 300ms 延迟现象,也就没怎么在意。

时至今日想动笔总结遇到了若干次的“滑动穿透”问题的时候,搜集资料的偶然间得以解惑 300ms 的前世今生。

移动端 click 的 300ms 延迟

那么,这 300ms 延迟到底是从哪里来的呢?

时间要追溯到 2007 年初代 iPhone 发布前夕,苹果为了解决“如何用手机这种小尺寸屏幕来显示 PC 端网页”这个问题,提出了很多聪明的约定(convention)。而后因为 iPhone 的大获成功,这些约定被各大手机浏览器争相效仿。

这些约定之中,双击缩放(Double Tap to Zoom) 就是 300ms 的“元凶”——当用户在页面上 click 的时候,浏览器为了判断这个用户操作是单击还是双击,会等待 300-350ms 。如果 300ms 内,发生了第二次 click 事件,那么视为双击;否则为单击,等 300ms 时间过去之后,才触发 click 事件。

在那个还不存在响应式设计和双指缩放(Pinch to Zoom)的时代,这个延迟是一个合理的预防措施。但不幸的是,这 300ms 的延迟已经成为用户觉得 web 应用比 native 应用更慢、性能不及后者的主要原因之一。诸如,链接、按钮、多选框等基于 click 交互的元素,以及 JS 对 click 事件的监听,都因此受到影响。

幸运的是,浏览器开发商(vendor)和开发者都注意到了这个问题,提出了一些解决方案。

解决方案

方案一、👎 禁用缩放

这里要注意区分:“双击缩放”(Double Tap to Zoom)和“双指缩放”(Pinch to Zoom)。为了兼顾消除 300ms 延迟和不损害可用性和可访问性,我们应该抛弃双击缩放、拥抱双指缩放。

方案二、👍 视窗宽度设置为设备宽度

该解决方案的“禁止双击缩放”是遵守如下规则的:

  • 当页面设置了视窗宽度为设备宽度且是初始尺寸(页面尚未缩放),此时,双击缩放才是被禁止的。
  • 如果视窗尺寸不是初始尺寸(页面已经缩放),双击缩放是被允许的。
  • 为了在用户结束缩放后仍能 fast-click ,缩小时,只能缩小到初始尺寸,而不是最小尺寸。

方案三、👍 指针事件(Pointer Events)

在只有 IE 支持指针事件的初期,诞生了不少指针事件的 polyfill 解决方案。在仍不支持指针事件的浏览器上,这是一种变通的方式。

shim VS polyfill

  1. 一个 shim 是一个库,它将一个新的 API 引入到一个旧的环境中,而且仅靠旧环境中已有的手段实现。
  2. polyfill 就是浏览器 API 的 shim 。 它用于实现浏览器并不支持的原生 API 的代码,是抹平新旧浏览器对原生 API 支持差异的封装。通常,polyfill 会先检查当前浏览器是否支持某个 API,如果不支持的话就加载它自己的实现,然后新旧浏览器就都可以使用这个 API 了。相当于“打补丁”,“刮腻子”。

方案四、👍 轻量级库 FastClick

关于“始作俑者” Safari

起承转折

(2013) 300 毫秒点击延迟的来龙去脉一文中提到的 IOS 特有的双击滚动(Double Tap to Scroll):仍存在、并没有像原文猜测的那样消失。(亲测 IOS 10.2.1 Safari 已设置 <meta name="viewport" content="width=device-width"> 的页面在屏幕上或下 1/4 处双击仍能滚动。)

起初看到「2016 年 3 月发布的 IOS 9.3 移除了 300ms 延迟、从而实现了“fast-tap” 」时,我还欣慰地想道:最先提出“双击缩放”约定的苹果,在最后也顺应了历史潮流嘛。但是接着看到「IOS10 无视了禁用缩放(user-scalable)」我的内心瞬间黑人问号:“???”。

后来,静静地看了两篇文章(Safari zoom gesture's comeback in iOS 10How to disable viewport scaling in iOS 10? You don't.),做了点 <meta> 标签的测试。

测试结果

测试环境:IOS 10.2.1

  1. 只设置 <meta name="viewport" content="user-scalable=no"> 和不设置没有任何区别——user-scalable=no 被完全无视。
  2. 只设置 <meta name="viewport" content="width=device-width"> ,和方案二里的描述一致,仍可以在初始尺寸下禁用双击缩放。
  3. 只设置 <meta name="viewport" content="initial-scale=1.0"> ,初始状态和“测试2”很像,但是仍存在双击缩放,即仍有 300ms 延迟。
  4. 设置 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 或者 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> ,和只设置 width=device-width 并无显著差异。
  5. 为某个 <a> 链接设置 touch-action: manipulation; ,可以禁用该元素上的双击缩放。

要点如下:

  1. user-scalable=no 完全起不到禁止缩放的作用,width=device-width 仍能且仅能禁止双击缩放。
  2. 只设置 meta 无法完全禁用缩放,双指缩放总是可行的。

暮然回首

冷静下来后,重新审视上述变故,发现其实是两回事。前面提到过“我们应该抛弃双击缩放、拥抱双指缩放”,苹果没有打破这个原则。只是,苹果出于可访问性考虑,直接任性地完全无视了 user-scalable=no

Accessibility
Pinch-to-zoom is always enabled for all users. The viewport setting for user-scalable is ignored.

当然,这导致了觉得应该一切尽在掌控、想要完全禁用缩放以避免破坏布局的开发者的怨言。如果,你还是想完全禁用缩放,可以参考 SO 上的这个回答

解惑

最开始提到过,我至今没有遇到过这个问题。对这个现象我推理如下:

我的肾机在开发移动端的半年间只在近期做过一次系统升级(目前已升到 10.2.1)。之前使用的具体的版本号已经无从得知了(P.S. 如果有谁知道怎么查看肾机本机上的版本更新历史,请务必告诉我233),但是更新到 IOS10 之前,我一直有使用 9.3+ 才支持的 Night Shift 功能,也就是说升级之前的系统版本号肯定在 9.3 或者以上。

而我写移动端页面的时候,惯例会 meta:vp 然后 Tab 生成 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">。在 IOS10 之前,这行代码还是能够禁用页面的缩放的,也就不存在 300ms 的延迟问题了。

这就是为什么之前我本机测试的时候一直没有遇到传说中的 300ms 延迟现象的原因了。

参考

  1. (2013) 300 毫秒点击延迟的来龙去脉
  2. (2014) 5-ways-prevent-300ms-click-delay-mobile-devices
  3. (2015) Implement viewport-width-based fast-click heuristic
  4. (2016) 无线端浏览器 click 事件 300ms 延迟
  5. (2016) 300ms-tap-delay-gone-away
上一篇 下一篇

猜你喜欢

热点阅读