最近在做项目的过程中,发现了一个由来已久的传说,300ms延迟问题,可能很多同学没听过,也可能遇到过但不知道怎描述。这个问题导致了很多体验性BUG,让前端背锅,所以今天我就曝光出来,以免大家以后遇到心中有数。
一、移动端300ms点击延迟
一般情况下,如果没有经过特殊处理,移动端浏览器在派发点击事件的时候,通常会出现300ms左右的延迟。也就是说,当我们点击页面的时候移动端浏览器并不是立即作出反应,而是会等上一小会儿才会出现点击的效果。在移动WEB兴起的初期,用户对300ms的延迟感觉不明显。但是,随着用户对交互体验的要求越来越高,现今,移动端300ms的点击延迟逐渐变得明显而无法忍受。
那么,移动端300ms的点击延迟是怎么来的呢?
问题由来
这要追溯至2007年初。苹果公司在发布首款iPhone前夕,遇到一个问题:当时的网站都是为大屏幕设备所设计的。于是苹果的工程师们做了一些约定,应对iPhone这种小屏幕浏览桌面端站点的问题。
这当中最出名的,当属双击缩放(double tap to zoom),这也是会有上述300毫秒延迟的主要原因。
双击缩放,顾名思义,即用手指在屏幕上快速点击两次,iOS自带的Safari浏览器会将网页缩放至原始比例。那么这和300毫秒延迟有什么联系呢?假定这么一个场景。用户在iOS Safari里边点击了一个链接。由于用户可以进行双击缩放或者双击滚动的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此,iOS Safari就等待300毫秒,以判断用户是否再次点击了屏幕。鉴于iPhone的成功,其他移动浏览器都复制了iPhone Safari浏览器的多数约定,包括双击缩放,几乎现在所有的移动端浏览器都有这个功能。之前人们刚刚接触移动端的页面,在欣喜的时候往往不会care这个300ms的延时问题,可是如今touch端界面如雨后春笋,用户对体验的要求也更高,这300ms带来的卡顿慢慢变得让人难以接受。
也就是说,移动端浏览器会有一些默认的行为,比如双击缩放、双击滚动。这些行为,尤其是双击缩放,主要是为桌面网站在移动端的浏览体验设计的。而在用户对页面进行操作的时候,移动端浏览器会优先判断用户是否要触发默认的行为。
那有什么办法可以解决这个问题呢?
浏览器开发商的解决方案
浏览器开发商要对移动端浏览器本身的设计进行改善,以提供长远的解决方案。
目前,浏览器开发商的解决方案主要有一下三种方案:
方案一:禁用缩放
当HTML文档头部包含如下meta标签时:
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
表明这个页面是不可缩放的,那双击缩放的功能就没有意义了,此时浏览器可以禁用默认的双击缩放行为并且去掉300ms的点击延迟。
这个方案有一个缺点,就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。但是通常情况下,我们还是希望页面能通过双指缩放来进行缩放操作,比如放大一张图片,放大一段很小的文字。
方案二:更改默认的视口宽度
一开始,为了让桌面站点能在移动端浏览器正常显示,移动端浏览器默认的视口宽度并不等于设备浏览器视窗宽度,而是要比设备浏览器视窗宽度大,通常是980px。我们可以通过以下标签来设置视口宽度为设备宽度。
<meta name="viewport" content="width=device-width">
因为双击缩放主要是用来改善桌面站点在移动端浏览体验的,而随着响应式设计的普及,很多站点都已经对移动端坐过适配和优化了,这个时候就不需要双击缩放了,如果能够识别出一个网站是响应式的网站,那么移动端浏览器就可以自动禁掉默认的双击缩放行为并且去掉300ms的点击延迟。如果设置了上述meta标签,那浏览器就可以认为该网站已经对移动端做过了适配和优化,就无需双击缩放操作了。
这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。
方案三:CSS touch-action
网上很多文章把这个方案归结为指针事件,这令我很疑惑。
以我的理解来看,指针事件的提出并不是为了解决300ms点击延迟的,而是为了使用一个单独的事件模型,对鼠标、触摸、触控等多种输入类型进行统一的处理。也就是说,移动浏览器不用再为不同的输入设备设计不同的事件,网页的开发者也不用再为不同输入类型的设备写不同的事件响应代码,而是通过统一的指针事件就可以开发出跨不同输入类型终端的应用。
跟300ms点击延迟相关的,是touch-action这个CSS属性。这个属性指定了相应元素上能够触发的用户代理(也就是浏览器)的默认行为。如果将该属性值设置为touch-action:none,那么表示在该元素上的操作不会触发用户代理的任何默认行为,就无需进行300ms的延迟判断。
而设置这个CSS属性与否,指针事件应该都是可以工作的。所以,网上的文章令我很疑惑,现有的解决方案
「真正解决方案」
要解决300ms点击延迟的问题,从长远来说,自然还是得浏览器开发商提供统一的最终的解决方案。但是,到目前为止,以上三种方案并不能提供很好的兼容性,对于方案一和方案二,Chrome是率先支持的,Firefox紧随其后,然而令Safari头疼的是,它除了双击缩放还有双击滚动操作,如果采用这种两种方案,那势必连双击滚动也要一起禁用;对于方案三,IE是支持的,但是其他浏览器支持不完善。
所以,在浏览器开发商最终统一的解决方案出来之前,我们还有一些基于Javascript的现成的解决方案可以用。
方案一:指针事件的polyfill
现在除了IE,其他大部分浏览器都还不支持指针事件。有一些JS库,可以让我们提前使用指针事件,比如:
Google的Polymer
微软的HandJS
Rich-Harris的Points
然而,我们现在关心的不是指针事件,而是与300ms延迟相关的CSS属性touch-action。由于除了IE之外的大部分浏览器都不支持这个新的CSS属性,所以这些指针事件的polyfill必须通过某种方式去模拟支持这个属性。一种方案是JS去请求解析所有的样式表,另一种方案是将touch-action作为html标签的属性。
方案二:FastClick
FastClick是FT Labs专门为解决移动端浏览器300毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉。
二、点击穿透问题
说完移动端点击300ms延迟的问题,还不得不提一下移动端点击穿透的问题。可能有人会想,既然click点击有300ms的延迟,那对于触摸屏,我们直接监听touchstart事件不就好了吗?
使用touchstart去代替click事件有两个不好的地方。
第一:touchstart是手指触摸屏幕就触发,有时候用户只是想滑动屏幕,却触发了touchstart事件,这不是我们想要的结果;
第二:使用touchstart事件在某些场景下可能会出现点击穿透的现象。
什么是点击穿透?
假如页面上有两个元素A和B。B元素在A元素之上。我们在B元素的touchstart事件上注册了一个回调函数,该回调函数的作用是隐藏B元素。我们发现,当我们点击B元素,B元素被隐藏了,随后,A元素触发了click事件。
这是因为在移动端浏览器,事件执行的顺序是touchstart>touchend>click。而click事件有300ms的延迟,当touchstart事件把B元素隐藏之后,隔了300ms,浏览器触发了click事件,但是此时B元素不见了,所以该事件被派发到了A元素身上。如果A元素是一个链接,那此时页面就会意外地跳转。
深圳 · 龙岗 · 大运软件小镇22栋201
电话:400 182 8580
邮箱:szhulian@qq.com