苹果发布IPhone时遇到一个问题——早期的网站都是针对PC设计的,手机屏幕尺寸很小,在手机端展示时页面内容的放大缩小是最常见的操作。所以IPhone规定,手指在屏幕快速双击时会放大或者缩小页面。如何判定手指是双击而不是单击两次呢?以两次敲击屏幕时间间隔是否超过约300ms来判定。
所谓的300ms延迟,指的是用户在点击屏幕之后(在touchend事件触发后),等待300~400ms才触发click事件。
举个例子,假设页面上有个链接,当用户点击链接时,浏览器无法立即判定用户是准备双击屏幕执行页面放大缩小,还是打算点击链接执行跳转。浏览器需要等待约300ms,如果300ms内有第二次点击,则认为是双击缩放页面。如果没有第二次点击才执行第一次点击的跳转。
在早起移动端发展还未起步的时候,用户能用手机上网已经很开心了不会挑剔300ms延迟,而现在这种延时带来的迟滞感对用户来说是无法忍受的。
苹果打开了移动端世界的大门,移动端safari带来了双击缩放和单击300ms延迟。其他移动端厂商抱苹果大腿,纷纷效仿。
谷歌意识到延迟是为了实现双击缩放,如果用户禁用了双击缩放浏览器就不需要再单击延迟。
<meta name="viewport" content="user-scalable=no"> <meta name="viewport" content="initial-scale=1,maximum-scale=1">
所以对于早期的安卓和苹果浏览器,只要HTML加入上面代码禁止了页面缩放,对于用户的单击就不做延迟处理。
后来谷歌觉得为了不延迟而禁用页面缩放的操作有些太傻(双击缩放和双指缩放对手机用户来说是刚需),于是规定只要设置了移动端页面的特征meta就不再延迟,不需要禁用页面缩放。其他厂商和苹果也纷纷跟进。
<meta name="viewport" content="width=device-width">
这样规定的思路是这样的:如果加了上面的meta,表示当前页面本来就是适合手机展示的页面(非PC排版在手机展示),缩放自然而然不是刚需,没必要为了缩放而加个延迟。
可能是苹果和谷歌觉得为了解决缩放搞这么复杂不值得,也可能是开发者乱加meta总是喜欢禁用页面缩放让厂商觉得违背了初衷。经过测试,发现IOS和安卓浏览器推翻了前面的约定,最新的浏览器上遵循下面的规律:
<meta name="viewport" content="user-scalable=no">
依然能双指缩放,双击还原。浏览器厂商也提供了一种使用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>