click延迟300ms解决方案

为何存在300ms延迟

起初做移动端H5时发现对于click事件IOS会延迟300ms,不是很理解为什么移动端网页已经热手很久了还会存在常规的click事件支持度都不高的情况,100ms用户就会觉得卡顿,300ms是致命的!在没有深入了解原由之前我直接转向用touchstart来替代click,尝试写了两个项目就觉得这样鲁莽的替代不适用于所有情况,存在两个问题:

1.点击穿透问题
2.滑动操作会触发touchstart

先说点击穿透问题,事件的发生顺序是touchstart=>touchend=>click,比方正在用vue做单页面开发,当前展示的组件某个button利用了touchstart来触发切换了组件,之后click触发的时候前一个button已经不在了,这个click就派发给了当前组件同一位置的元素,如果恰好这个元素是个链接,那么就会触发跳转,这个可以用阻止默认行为来处理,比如用vue的话,在事件上加上.prevent就不会点击穿透

第二个问题滑动操作会触发touchstart,同个页面内标签切换还能接受,若是跳转功能,用户在滑动中突然页面跳转了,使用感受太烂!!!

研究click延迟的问题需要追踪发展史,事由酱紫:第一版iPhone发布之前还不存在响应式页面开发,当时的网站都是依照大屏幕设计的,那么如何解决在小屏幕上浏览大屏幕网站,苹果的工程师做了一些约定,有名的是双击缩放,即在iOS自带的Safari上快速点击两次,会将网页缩放至原始比例

如何产生300ms延迟的?因为即然存在双击缩放和双击滚动的功能,那么当用户点击一次的时候没办法立即执行click事件,因为不知道用户的意图是单击还是双击,所以就空留一定的时间来确认用户有没有再次点击,300ms就产生了,鉴于苹果的成功,其他厂商沿用了一些约定,其中就包括双击缩放功能,几乎所有的移动端浏览器都有这个功能,但是随着touch端界面如火如荼的发展,用户对体验要求也逐渐提高,300ms的点击延迟已经无法忍受

开发商解决方案

开发商意识到这个问题,也逐步的出了一些解决方案

方案一:禁止缩放

即然300ms就是由双击功能产生的(几乎所有浏览器都支持双击缩放,iOS除此之外还有个双击滚动的功能),貌似直接禁止缩放功能可以解决,安卓的chrome率先实现了,随后安卓的Firefox也实现了,通过添加meta

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

这样后页面就不能缩放了,所以就不存在双击缩放了,这样有个问题就是直接禁用的是缩放功能,就代表整个页面都没有缩放功能,那么想放大一个图片或者比较小的文字,sorry办不到!!so,安卓平台的chrome和Firefox提供的禁用缩放优化,仅使用于web游戏等特定的情境

方案二:修改窗口可视宽度

最早之前出现双击缩放功能是为了解决小屏幕浏览器浏览为大屏幕浏览器设计的网站,而且当时在渲染桌面站点的时候,使用980像素的视口宽度,并非移动端实际的视口宽度(其他厂商也这样),如果将可视宽度改为实际的移动端视口宽度,也就是响应式设计,即然网页已经是实际的移动端可视宽度了,客观来说那么就不需要双击缩放功能了,chrome 32版本中就实现了这样一个标识:

1
<meta name='viewport' content='width=device-width'>

当存在这样的标识就知道应用了响应式,自动忽略点击缩放的功能,但是并没有完全禁用缩放,还可以实现双指缩放,但是目前只有chrome 32版本实现了此功能

对于iOS而言如果想要实现此功能,不光抛弃双击缩放还要抛弃双击滚动功能,但是目前没有任何眉目

方案三:touch-action

touch-action属于指针事件里面的内容,但是它关系到300ms延迟点击,属性决定“是否触摸操作会触发用户代理的默认行为,这包括但不限于双指缩放等行为”,属性默认值auto,设置none可移除该元素300ms的延迟,目前只有internet explorer实现了指针事件:

1
2
3
4
5
6
button{
<!-- E11 -->
touch-action: none;
<!-- E10 -->
-ms-touch-action: none;
}

当前解决方案

目前为止,虽然厂商陆陆续续出了些解决方案,但是还是没有简单通用的方法,开发者带来了一些暂时性的JS跨平台方案,可以分为两大类:指针事件polyfill和点击事件上的自定义处理

指针事件的polyfill

比较流行的几个是:

chrome 的 polymer
微软 的 handJS
Rich-Harris 的 Points

这些ployfill在非IE中模拟CSS的touch-action属性,非IE浏览器是不会识别这个属性的,那么需要利用JS去寻找开发者有没有设置toucn-action:none,handJS的做法是请求并解析所有的样式表,有些恐怖

polymer的做法是为元素设置touch-action属性,不用CSS:

1
<a href="###" touch-action='none'>chrome</a>

用这种方式来解决300 ms延迟,要么是资源密集型方案,要么是touch-action属性非标准模式,暂时没有做深入了解,想要研究可以链接跳过去

点击事件自定义处理

方式上有两种:

一种是touchend之后通过DOM自定义事件立即触发一个模拟click事件,并禁止之后300ms会发生的click事件,FastClick就是这么干的,我的项目中直接用了这个轻量级库(10K),在vue中开发直接在main.js中配置:

1
2
import FastClick from 'fastclick'
FastClick.attach(document.body);

直接用在body上这样整个页面就都可以利用啦

另一种是通过监听tap而非click事件绕过300ms延迟,tappy以及tap.js都是这样做的,两者比FastClick更轻,不过暂时没有使用过