最近遇到奇异的bug,在ios 11.3移动端页面 input输入框第一次触摸可以弹起键盘,后续再触摸需要很难弹起键盘,或者需要在输入框停一会才能弹起键盘。
bug复现条件:
一、ios 11.3中app的webview为 UI WebView
二、在项目中使用了FastClick.js,页面包括输入框.
发现源头问题:
在碰到问题脑子第一想法这不就是click延迟300ms的现象吗?所以就想到是不是FastClick.js导致,注释掉后发现bug现象消失了,代码如下:
1 | define(['zepto'], function ($) { |
但是这是为什么呢?我们一起看看为什么要加上FastClick,这个库解决了什么问题?
- click 300ms延迟:浏览器click会比touch延迟300ms触发
- click穿透现象:当两个div同处一个position,上层div绑定touch,下层div绑定click,当上层div触发touch消失后,可能会触发下层div的click事件
既然Fastclick是为了解决这两类问题,其实现原理如下图所示:
fastclick利用捕获顶层dom元素(如:body,html等)的click事件,拦截所有的click请求进行判断:是否有touch触发、是否需要阻碍click事件(stopImmediatePropagation)等。
分析问题解决方案:
步骤一:input无法聚焦弹出键盘,fastclick中有一块判断当前元素targetElement是否需要needsFocus,看看其方法的实现:
1 | FastClick.prototype.needsFocus = function(target) { //判断当前元素是否需要focus |
步骤二:看到needsFocus下执行了什么?在touchEnd方法中,代码块如下:
1 | if (this.needsFocus(targetElement)) {if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) { |
步骤三:focus方法分析(包含解决方案),如下:
1 | FastClick.prototype.focus = function(targetElement) { |
原理分析
OK,上真机iphoneX验证bug已经消失了,但是我们并不知道为什么在ios 11.3会出现该问题,秉着探索真理的一颗心(ZZZZ),到github去查看FastClick的issues列表,果然发现早有人提出bug了,如下图:
下方有评论如下:
A:说framework7框架那边已经有解决方案啦,点击这里。
另外一位仁兄的解决方案和我类似,修改focus方法。
因此跳到framework的issue中的解决方案,解决方案:点击这里,描述如下:
跳过去stackoverflow后,其实根本源头已经查到了,ios 11.3更新 Safari 11.1,支持新web API :允许对事件支持 {passive: false}
被动模式,减少滚动屏幕的性能损耗和奔溃。
passive mode解析
那么新的问题来了,{passive: false}是什么玩意?来,我们先看看它的使用方式:
1 | document.addEventListener('touchmove', function(e) { |
按照以往我们对添加事件监听的方法三个参数的认知,如下:
1 | document.addEventListener(type , callback, capture); //type是事件类型,callback是执行函数, capture是否进行捕获/冒泡,默认为false |
Passive event listeners是2016年Google I/O 上同 PWA 概念一起被提出,但是同PWA不同,Passive event listeners 的作用很简单,如果用简单一句话来解释就是:提升页面滑动的流畅度。
1 | target.addEventListener(type, listener[, options]); |
为什么增加支持这个属性会导致添加fastclick后input输入框很难弹出键盘?
在ios更新日志了,写到了“Updated root document touch event listeners to use passive mode improving scrolling performance and reducing crashes.”
翻译过来就是:针对document的touch事件监听添加passive配置,即是:{passive: true},会永远不调用event.preventDefault(),以此来提高滚动性能。
源头推测:
fastclick是采用拦截click和监听touch事件去实现的,里面包括对tagetElement的focus方法重写,因此在11.3之前可能event.preventDefault生效了,同时用setSelectionRange是可以聚焦input的。
另外一个bug也是由这个导致的是:
在iOS11.3的UI webview使用fastclick.js,页面有个按钮点击事件,当app或锁屏超过几分钟时间,回到页面会导致click事件失效。
解决方案为:
1 | var passiveListener = (function checkPassiveListener() { |
参考资料
- 《EventTarget.addEventListener()增加passive属性说明——web MDN API网站》
- 《fastclick github issues#548》
- 《stackoverflow ——touchmove e.preventDefault失效问题》
- 《Safari 11.1更新日志》
- 本文作者: Qborfy
- 本文链接: https://www.qborfy.com/share/iOS11-fastclick-js.html
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!