前言
有一次我在制作移动端h5页面时,遇到一个很奇怪的bug,ios端在input输入框第一次可以正常触发弹起键盘,但之后却需要长按且很困难才能再次触发该行为,于是我便去仔细了解了下这个怪异的问题,便记录下来。

抓出捣蛋的坏蛋
当时一开始我怀疑是自己的代码写了bug,但是我排查了下,我并没有去编写关于输入框聚焦事件之类的工作代码,苦思冥想,终于在搜索引擎里,看到了类似的问题,文中也提到了这种诡异情况,没错!最后的矛头指向了fastclick.js!于是我屁颠颠的跑去了github上查看issues,原来这个问题已经早有开发者提出:

确定bug发生条件
通过查看该issue,基本可以确定该bug发生的条件了:
- 移动端为IOS且版本在11.3+的webview中。
- 引入使用了fastclick.js解决300ms延迟。
- 页面使用了输入框控件。

解决的方法
仔细查看issue的回复,发现已经有人提出一种解决方法:
根据图片,在fastclick.js源码中进行修改,增加元素focus方法强制弹起,的确解决了这个bug,但是目前不知道这样修改会不会导致其他问题。
深入fastclick.js
经过查阅文档文章可以明确的了解到fastclick.js主要是解决移动端开发的两个问题:
- click事件300ms延迟
- 点击穿透问题
300ms延迟
在移动设备上触发click事件,浏览器会等待300ms,以此来判断用户是否想执行双击操作,为了解决这个问题,有两种已知的解决方案:
- 将touch系列事件绑定在document上,通过计算touch事件触发的时间及位置来判断是否移动端触发事件,如大家熟悉的zepto.js的tap事件。
- 当检测到touchend事件后,通过dom自定义事件立即模拟一个click事件,并用preventDefault阻止300ms后的click事件触发,也就是fastclick.js使用的方案。
fastclick.js的具体工作流程如下:

点击穿透
打个比方有多个元素重叠在同个位置,上层元素绑定touch事件,下层绑定click事件,当上层触发touch事件将有可能触发下层的click事件,这种现象就叫点击穿透。在简书有一篇文章专门讲了如何解决点击穿透的,fastclick.js捕获顶层dom元素(html,body)的click事件,拦截所有请求然后进行判断,是否有touch触发以及阻碍click事件等。
追溯问题缘由
仔细查看大量文章后,终于缕清了一些思路,在一篇关于framework的issue的解决方案中,有位开发者提到:

我们通过这个回答和stackoverflow链接可以大概了解到发生了什么:

IOS11.3将Safari升级到了11.3,支持新的web API,允许对事件支持(passive:false)被动模式,减少屏幕滚动时的性能损耗和减少防止崩溃现象,且在IOS更新日志中提到:
Updated root document touch event listeners to use passive mode improving scrolling performance and reducing crashes.
针对document的touch事件监听添加(passive:true)被动模式的配置,且根据MDN上的说明,开启此参数后,listener将永远不会调用preventDefault()。

由于fastclick.js采用拦截click事件和监听touch事件去实现的,且对原生focus方法进行了重写,所以可能因此导致了无法重复调起聚焦事件。
另外一个bug
还发现部分开发者反映了另外一个问题,也是因为这个导致的,当静置或锁屏一段时间后,页面将不再响应任何click事件,我们可以通过渐进增强设置被动模式即可解决该bug:

参考资料
- IOS11.3 fastclick.js相关bug
- What’s New in Safari -ver 11.1
- Can’t prevent ‘touchmove’ from scrolling window on IOS
- can not bring up keyboard instantly in ios 11.3(issue)
- fastclick解析与ios11.3相关bug原因分析
总结
终于我们又踩过一个坑,跨端兼容一直是件需要耐心的事情,耐心的面对成长路上带来的艰辛,一定会有所收获的!