目录

彻底弄清移动端300ms延时

延时的由来

苹果发布IPhone时遇到一个问题——早期的网站都是针对PC设计的,手机屏幕尺寸很小,在手机端展示时页面内容的放大缩小是最常见的操作。所以IPhone规定,手指在屏幕快速双击时会放大或者缩小页面。如何判定手指是双击而不是单击两次呢?以两次敲击屏幕时间间隔是否超过约300ms来判定。

所谓的300ms延迟,指的是用户在点击屏幕之后(在touchend事件触发后),等待300~400ms才触发click事件。

举个例子,假设页面上有个链接,当用户点击链接时,浏览器无法立即判定用户是准备双击屏幕执行页面放大缩小,还是打算点击链接执行跳转。浏览器需要等待约300ms,如果300ms内有第二次点击,则认为是双击缩放页面。如果没有第二次点击才执行第一次点击的跳转。

在早起移动端发展还未起步的时候,用户能用手机上网已经很开心了不会挑剔300ms延迟,而现在这种延时带来的迟滞感对用户来说是无法忍受的。

与延时的战斗

阶段1

苹果打开了移动端世界的大门,移动端safari带来了双击缩放和单击300ms延迟。其他移动端厂商抱苹果大腿,纷纷效仿。

阶段2

谷歌意识到延迟是为了实现双击缩放,如果用户禁用了双击缩放浏览器就不需要再单击延迟。

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

所以对于早期的安卓和苹果浏览器,只要HTML加入上面代码禁止了页面缩放,对于用户的单击就不做延迟处理。

阶段3

后来谷歌觉得为了不延迟而禁用页面缩放的操作有些太傻(双击缩放和双指缩放对手机用户来说是刚需),于是规定只要设置了移动端页面的特征meta就不再延迟,不需要禁用页面缩放。其他厂商和苹果也纷纷跟进。

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

这样规定的思路是这样的:如果加了上面的meta,表示当前页面本来就是适合手机展示的页面(非PC排版在手机展示),缩放自然而然不是刚需,没必要为了缩放而加个延迟。

阶段4

可能是苹果和谷歌觉得为了解决缩放搞这么复杂不值得,也可能是开发者乱加meta总是喜欢禁用页面缩放让厂商觉得违背了初衷。经过测试,发现IOS和安卓浏览器推翻了前面的约定,最新的浏览器上遵循下面的规律:

  1. 页面总是能放大缩小。设置meta禁用页面缩放是无效的,比如即使页面加了<meta name="viewport" content="user-scalable=no"> 依然能双指缩放,双击还原。
  2. 只要页面处于放大状态,单击就一定有延迟;只要页面处于未放大的原始状态,单击就一定没有延迟。在一般情况下,不管是pc端页面还是移动端页面,不管有没有设置meta,不管有没有禁用缩放,用户打开页面默认就处于原始状态,是没有延迟的。当用户双指放大后,单击才出现延迟。

浏览器厂商也提供了一种使用CSS来彻底取消延迟的方案。只要CSS里做了如下设置,不管页面是否处于放大状态,都会停止单击延迟。

html {
  touch-action: manipulation;
}

经测试,双击页面依然会缩放,但单击链接会立即跳转。

旧版本浏览器处理

对于不同时期的移动端浏览器,取消单击延时有不同的写法。在处理时不仅需要考虑不同版本浏览器的兼容性,各自方案本身又有各自的缺点。如果需要兼容早期的浏览器,可以使用 fastclick 这个库 。

<script type='application/javascript' src='/path/to/fastclick.js'></Script>
<script>
  document.addEventListener('DOMContentLoaded', function() {
		FastClick.attach(document.body);
	}, false); 
</Script>

关于fastclick的原理,我们在下一篇文章讲解,并且会实现一个简化版的fastclick。

延迟的测试代码

分别开启以下代码中的注释,在IOS safari、安卓原生浏览器、UC浏览器上分别测试。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 
  <meta name="viewport" content="user-scalable=no">
  <!-- <meta name="viewport" content="initial-scale=1,maximum-scale=1"> -->
  <!-- <meta name="viewport" content="width=device-width"> -->
 
  <!--
  <style>
  html {
  	touch-action: manipulation;
	}
  </style>
  -->
</head>
<body>
  <div> 
    <a id="link1" href="#1">链接#1</a> 
    <a id="link2" href="#2">链接#2</a>
  </div>
  <div id="log"></div>
 
 <script>
    const $ = s => document.querySelector(s)
    const log = str => $('#log').innerText = str 
    let t
 
    $('#link1').ontouchend = e => {
      t = Date.now()
    }
 
    $('#link2').ontouchend = e => {
      t = Date.now()
    }
 
    window.onhashchange = () => {
      log(`link: ${Date.now() - t}ms`)
    }
  </Script>
</body>
</html>