作为前端开发,工作中不可避免的要接触 input,要基于 input 做一些定制化开发,但因为安卓 ios 系统的差异性,宿主 app 的 webview 选择(特指 ios),app 的历史遗留问题等等,会出现大量的兼容性问题
下面,对我开发过程中遇到的问题以及解决方案进行记录,希望能帮到你们
1. ios webview 类型
ios 下不同的 app 内置的 webview 可能不同,导致 input 行为不一致,所以我们先大致了解一下,目前 ios 系统下有两种 webview:WK 和 UI
ios8 之前,一直使用的是 UI,但是从 ios8 开始,新增了 WK 类型,相比 UI,WK 加载速度更快,消耗内存更少,并且支持更多 h5 的特性,所以各大公司基本都会对此进行升级,但不排除有些 app 因为各种历史原因还在使用 UI,比如我们,目前我们的 h5 页面运行在多个 app 内,有 UI 也有 WK,自然在开发的时候做一些兼容判断
判断 webview 类型
// 暂没发现好的方法来判断 webview 的类型,除非 ios 开发告诉你
// 下面这个方式是通过某些只有 wk 支持的 h5 新特性来判断
function getIosWebviewType() {if (navigator.platform.substr(0, 2) === 'iP') {// iOS (iPhone, iPod or iPad)
const lte9 = /constructor/i.test(window.HTMLElement);
const nav = window.navigator; const ua = nav.userAgent;
const idb = !!window.indexedDB;
if (ua.indexOf('Safari') !== -1 && ua.indexOf('Version') !== -1 && !nav.standalone) {return 'Safari'} else if ((!idb && lte9) || !window.statusbar.visible) {return 'UI'} else if ((window.webkit && window.webkit.messageHandlers) || !lte9 || idb) {return 'WK'}
}
return 'unknown'
}
2. autofocus 失效
不管是 h5 规范还是在 caniuse 上面查,这个属性都是支持的,但你在真机上面测试的时候,很多情况下,并不是你想要的效果
目前我测试情况如下:
-
ios
- UIWebview,可以聚焦,有光标,能呼起键盘
- WKWebview,可以聚焦,有光标,<u> 但呼不起键盘 </u>
-
安卓
- 暂时没发现问题(但不绝对)
网上了解到的原因是,苹果希望由用户来触发,输入这个操作是用户的意愿,而不是强制的,所以在新版本系统中禁止了这个属性,老版本的 UI 上面还没有这个限制
但庆幸的是,不管是安卓还是 ios,都支持在 touch 或者 click 事件中 <u> 同步 </u> 的执行 input.focus() 来聚焦并呼起键盘,
如果业务场景符合这种情况,那可以通过这种方式来做
onClick = () => {this.input.focus()
}
但如果在点击事件中有异步操作时,同 autofocus 属性,只能聚焦,不能呼起键盘
onClick = () => {setTimeout(() => {this.input.focus()
}, 1000)
// 或者
fetch('/api/get').then(() => {this.input.focus()
})
}
降级处理
如果业务场景不符合上面的情况,但又非要这个功能,有一种方案是,聚焦之后,强化一下聚焦的效果(因为原生的光标展示毕竟不明显)
- 设置一个聚焦的样式,在 input 上也好,在页面其他元素也好,由自己产品特色以及 UE 决定
- 适当的滚动页面,使其到视图中间
可参考
3. 聚焦问题
3.1 点击无法聚焦或者聚焦之后又失焦
大部分情况是移动端 300ms 问题引起的
一种是:app 使用 UIWebview 但是 h5 页面没引入 fastclick
另一种是:引入了 fastclick,但需要对 fastclick 的 focus 进行优化,改为
FastClick.prototype.focus = function (targetElement) {targetElement.focus();
};
3.2 光标无法聚焦到点击位置
这个大概率也是 fastclick 导致的,可参考:链接
3.3 已有聚焦 input 的情况下点击其他 input,会跳动
这个大概率也是 fastclick 导致,可参考上面那个链接
或者这样改 fastclick 源码,在 onTouchEnd 中新增判断,是否需要走原生聚焦逻辑
FastClick.prototype.onTouchEnd = function (event) {
// ...
if (targetTagName === 'label') {// ...} else if (this.needsFocus(targetElement)) {
// 新增
if (!this.needsFocusInput()) return false
// ...
}
// ...
};
// 新增
// 已有聚焦元素的情况下,直接走原生 input 聚焦逻辑
FastClick.prototype.needsFocusInput = function () {const focusInput = document.querySelector('input:focus')
return !focusInput
}
3.4 建议
上面这几个问题,大部分是 fastclick 导致的,但 fastclick 所解决的问题(300ms),目前大部分浏览器已经解决了,所以确认一下,如果你们的 app 不用 UIWebview 的话,那可以直接去掉 fastclick 了
fastclick github 说明:
Note: As of late 2015 most mobile browsers – notably Chrome and Safari – no longer have a 300ms touch delay, so fastclick offers no benefit on newer browsers, and risks introducing bugs into your application. Consider carefully whether you really need to use it.
4. 失焦问题
4.1 希望保持聚焦
有时候,希望点击页面其他地方的时候,input 保持聚焦状态,但浏览器默认行为是将 input 失焦
解决方案:在点击事件中,阻止默认行为
function onClick(e) {
// 你的事件处理代码
...
e.preventDefault();
// iphone 有的机型下,没有阻止掉默认行为,主动再聚焦一下
input.focus();}
4.2 希望主动失焦
安卓某些机型有以下问题
- 聚焦情况下,点击页面其他地方,不主动失焦
- 某些情况下,主动隐藏键盘之后,input 并没有失焦,但是用户触摸页面其他地方时,因为 input 还处于聚焦状态,会呼起键盘
针对性的解决方案也分两种
第一种:监听用户行为,主动失焦
const autoBlur = (e) => {
const target = e.target
const {tagName, className} = target
// 点击非 input 区域
if (tagName.toUpperCase() !== 'INPUT') {this.input.blur()
}
}
document.body.addEventListener('touchstart', autoBlur)
第二种:监听键盘高度变化,主动失焦
const onKeyboardChange = (resize) => {
// 有时候,比如 number 变成 text,或者系统自动在键盘上面加一些装饰,键盘并没有隐藏,但是触发了 resize
// 测试大部分机型,所有的键盘肯定大于 120 高度了,所以加一个限制
if (Math.abs(resize) < 120) return
const show = resize > 0
if (!show) {this.input.blur()
}
}
function getClientHeight() {return document.documentElement.clientHeight || document.body.clientHeight;}
// 记录原始高度
let originHeight = getClientHeight()
// 监听键盘变化
window.addEventListener('resize', () => {const resizeHeight = getClientHeight()
const resize = originHeight - resizeHeight
onKeyboardChange(resize);
originHeight = resizeHeight;
}, false)
5. 聚焦之后滚动到视图内
大部分情况下,系统会帮你将 input 滚动到视图内,少数情况下,需要我们自己设置
- 一种方案是,直接调用 scrollIntoViewIfNeeded api
- 另一种是,计算聚焦前后文档高度的变化差值,然后将 body 高度相应的调大,并且设置 scrollTop,<u> 这种情况应该是出现在 UIWebview 下 </u>
// 正常处理
input.addEventListener('focus', () => {setTimeout(() => {this.input.scrollIntoViewIfNeeded();
}, 300)
})
// UIWebview 下的处理
function getClientHeight() {return document.documentElement.clientHeight || document.body.clientHeight;}
const bodyHeight = getClientHeight()
const bodyOverflow = getComputedStyle(document.body).overflow
input.addEventListener('focus', () => {
document.body.style.overflow = 'auto'
setTimeout(() => {// alert(getClientHeight())
let height = bodyHeight - getClientHeight()
if (height < 0) height = height * -2
document.body.style.height = `${bodyHeight + height}px`;
document.body.scrollTop = height;
}, 300)
})
// 如果设置了高度,在 blur 时要设置回来
input.addEventListener('blur', () => {document.body.style.height = `${bodyHeight}px`;
document.body.style.overflow = bodyOverflow
})
加 setTimeout 是因为唤起键盘会有一个动画,我们需要在动画结束之后再滚动页面,否则计算出的高度不准
大部分情况下,键盘动画时间小于 300
6. 强制光标到末尾
input.addEventListener('touchend', e => {
const length = e.target.value.length
e.target.setSelectionRange(length, length);
e.preventDefault()
e.target.focus()})
7. 其他的一些问题
7.1 光标隐藏
某些场景下,需要自定义输入框,自定义光标等
安卓可以通过 opacity: 0; color: transparent 等将原生光标隐藏
但 ios 无法隐藏,只能将 input 框移动到视图外,先将 input 宽度设置足够大,然后左移:
input {
width: 1000px;
margin-left: -200px;
}
7.2 复制粘贴
安卓和 ios 都支持长按复制粘贴,但 ios 的前提是,光标必须在视图内,像上面那样将 input 左移之后,粘贴按钮不在视图内,无法粘贴
7.3 短信验证码(或其他数字)展示在系统键盘
新版 ios(应该是 12+,不确定)系统收到短信以后会直接将短信展示在键盘上面,用户一点就可以输入到 input,但老版不支持,需要用户主动去短信复制,然后再粘贴到 input,如果 input 是自定义的,需要注意上面问题
部分安卓机器(比如小米 6 +)收到短信,可直接点通知栏进行复制,同样会出现在键盘上,其他机型需要用户长按粘贴
8. 结语
上面基本是我开发过程中遇到的问题了,随着系统的迭代升级,很多问题会得到官方的解决,但是我们无法决定 app 是否升级,无法决定用户是否升级
所以,如果你们也遇到这些问题,希望可以给予你们启发,帮助