移动端300ms延迟问题

300ms延迟由来

2007 年初。苹果公司在发布首款 iPhone 前夕,遇到一个问题:当时的网站都是为大屏幕设备所设计的。于是苹果的工程师们做了一些约定,应对 iPhone 这种小屏幕浏览桌面端站点的问题。这当中最出名的,当属双击缩放(double tap to zoom),这也是会有上述 300 毫秒延迟的主要原因。

双击缩放,顾名思义,即用手指在屏幕上快速点击两次,iOS 自带的 Safari 浏览器会将网页缩放至原始比例。 那么这和 300 毫秒延迟有什么联系呢? 假定这么一个场景。用户在 iOS Safari 里边点击了一个链接。由于用户可以进行双击缩放或者双击滚动的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。 后来,其他移动浏览器都复制了 iPhone Safari 浏览器的多数约定,包括双击缩放。

研究表明当延迟超过 100 毫秒,用户就能感受到界面的卡顿。 然而,出于对手指触摸滑动的区分,移动端页面对于触摸事件会有 300 毫秒的延迟,导致多数用户感觉移动设备上基于 HTML 的 web 应用界面响应速度慢。浏览器开发商已经意识到这个问题,并已相继提出了一些解决方案。

1、禁止缩放

上文提到,之所以有300ms延迟,主要是为了判断用户是否会在第一次点击之后进行第二次点击,如果第二次点击与第一次点击间隔小于300ms,则认为是缩放操作,因此,如果禁止页面缩放,也就不需要等待300ms去判断用户的点击香味是否为缩放操作,在移动开发中,可通过meta标签禁止缩放

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

这种方法通过完全禁用缩放避免了300ms延迟,却大大降低了移动端页面的可用性和可访问性,比如,当你想要放大一张图片或者一段字体较小的文本,却发现无法完成操作。

2、 width=device-width Meta 标签

除了双击缩放的约定外,iPhone 诞生时就有的另一个约定是,在渲染桌面端站点的时候,使用 980 像素的视口宽度而非设备本身的宽度。所以,一张宽度为320x320像素的图片在设备宽度为320的iPhone4上并不占满屏幕宽度,在移动段开发中,我们可以通过 <meta> 标签来进行配置:

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

双击缩放的诞生解决了在移动设备上浏览桌面端站点的问题。既然站点内包含了 width=device-width 这一 <meta> 标签,也就意味着这个网站采用了响应式设计,因此也就消除了在该站点上可能潜在的双击缩放需求。

这一解决方案的另一个关键之处在于它只是去除了双击缩放,但用户仍可以使用双指缩放 (pinch to zoom)。可见,缩放功能并非被完全禁用,也就不存在可用性和可访问性的问题了。

3、指针事件 (Pointer Events)

指针事件最初由微软提出,现已进入 指针事件是一个新的 web 事件系列,相应的规范旨在使用一个单独的事件模型,对所有输入类型,包括鼠标 (mouse)、触摸 (touch)、触控 (stylus) 等,进行统一的处理。例如,你可以只去监听一个元素的 pointerdown事件,无需分别监听其 touchstart和 mousedown事件。

有一个和点击延迟直接相关的实现 —— 一个名为 touch-action的新 CSS 属性。根据规范,touch-action属性决定 “是否触摸操作会触发用户代理的默认行为。这包括但不限于双指缩放等行为”。从实际应用的角度来看,touch-action 决定了用户在点击了目标元素之后,是否能够进行双指缩放或者双击缩放。因此,这也相当完美地解决了 300 毫秒点击延迟的问题。

touch-action的默认值为 auto,将其置为 none即可移除目标元素的 300 毫秒延迟。例如,下面的代码在 IE10 和 IE11 上移除了所有链接和按钮元素的点击延迟。

a[href], button {
 -ms-touch-action: none; /* IE10 */
 touch-action: none;     /* IE11 */
}

但就目前而言,只有 Internet Explorer 实现了指针事件,不过近期 Chrome 也宣布了将在未来的版本中提供支持

当前解决方案

尽管浏览器开发商针对 300 毫秒延迟问题提出了一些解决方案,但目前并没有简单通用的方案。不过,已经有好多开发者考虑过这一问题,并带来了一些基于 JavaScript 的跨平台解决方案。这些方案可以归为两类 —— 针对指针事件的 polyfill 和“快速点击 (fast click)”。

1、polyfill

指针事件的 polyfill 比较多,以下列出比较流行的几个。
Google 的 Polymer
微软的 HandJS
@Rich-HarrisPoints

为避免 300 毫秒点击延迟,我们主要关心这些 polyfill 是如何在非 IE 浏览器中模拟 CSS touch-action 属性的,这其实是一个不小的挑战。由于浏览器会忽略不被支持的 CSS 属性,唯一能够检测开发者是否声明了 touch-action: none 的方法是使用 JavaScript 去请求并解析所有的样式表。HandJS 也正是这么做的,但不管是从性能上来看还是其他一些复杂的方面,这都会遇到问题。

Polymer 则是通过判断标签上的 touch-action 属性 (attribute),而非 CSS 代码。下面的代码展示了 Polymer 是如何在链接上模拟 CSS touch-action: none 属性的。

<a href="http://google.com" touch-action="none">Google</a>

2、fastclick

FastClickFT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。简而言之,FastClick 在检测到 touchend事件的时候,会通过 DOM 自定义事件立即触发一个模拟 click事件,并把浏览器在300 毫秒之后真正触发的 click事件阻止掉。

FastClick 的使用方法非常简单,在 window load 事件之后,在 <body>用FastClick.attach()即可。

window.addEventListener( "load", function() {
   FastClick.attach( document.body );
}, false );

推荐阅读更多精彩内容