正文
一、背景
最近在做一个移动端 Web 项目,在首页底部是有一个类似于 APP 导航栏(以下称 FootNav
),采用的 fixed
布局固定于底部。同时页面有一些 <input>
输入框(以下称 Input
)。
当聚焦于 Input
时,在 iOS 预期效果是没问题,但是在杀千刀的 Android 上,页面高度发生变化,导致 FootNav
固定在手机键盘上面,同时 FootNav
也直接挡住了输入框,交互体验非常的糟糕。
烦死了...
二、iOS 与 Android 键盘弹出收起的表现
先了解下背景,键盘的弹出收起,在 iOS 端与 Android 端的 WebView 中表现并非一致的。
1. 键盘弹出
- iOS
在 iOS 系统的键盘处于窗口的最上层。当键盘弹出时,WebView 的高度height
并没有发生改变,只是scrollTop
发生改变。页面可以滚动,且页面可滚动的最大限度为弹出键盘的高度,而只有键盘弹出时页面恰好也滚动到最底部时,scrollTop
的变化值为键盘高度。 - Android
在 Android 系统中,键盘也是处于窗口的最上层。键盘弹出时,页面高度发生变化,如果输入框在靠近底部的话,就会被键盘挡住,只有你输入的时候才会滚动到可视化区域。
2. 键盘收起
- iOS
当触发键盘上按钮收起键盘或者输入框以外的区域时,输入框会失去焦点,因此会触发输入框的blur
失焦事件。 - Android
当触发键盘上的按钮收起键盘时,输入框并不会失去焦点,因此不会触发输入框的blur
事件;触发输入框以外的区域时,输入框会失去焦点,触发输入框的blur
事件。
由于我并没有过多的深入了解两者的差异表现,以上内容来自此处。
三、寻找解决方案
针对 Android 设备做处理就行了,iOS 无需处理。
1. 方案一
处理方式:Input
聚焦隐藏 FootNav
,失焦时再将其显示出来。(同理,修改布局方式也是一样的)
首先这种处理思路是没毛病的,但是...
某些机型、某些输入法,收起键盘并不会触发输入框的
blur
失焦事件,导致该方案直接流产。
2. 方案二
监听页面高度的变化,利用这一点我们就可以处理 FootNav 的隐藏/显示了。
四、实现
思路很简单:首先进入页面时,先记录窗口的原始高度。每当 Input
聚焦时,设置 window.onresize
函数,当窗口宽高发生改变时便会触发。
以 React 为例:
class Home extends React.Component { constructor(props) { super(props) this.state = { showFootNav: true, initClientHeight: document.documentElement.clientHeight // 记录初始高度 } } // 判断 Android 设备 isAndroid() { const ua = navigator.userAgent.toLowerCase() return ua.includes('android') || ua.includes('linux') } // 监听窗口变化 listenWindowResize() { let that = this if (this.isAndroid()) { window.onresize = () => { const { initClientHeight } = that.state const curClientHeight = document.documentElement.clientHeight // 当前页面高度 if (curClientHeight < initClientHeight) { // 键盘弹出 that.setState({ showFootNav: false }) } else { // 键盘收起 that.setState({ showFootNav: true }, () => { window.onresize = null // 清空 onresize }) } } } } render() { const { showFootNav } = this.state // 以下 Input、FootNav 为自定义封装的组件 return ( <div> <Input onFocus={this.listenWindowResize.bind(this)} /> {showFootNav ? <FootNav /> : null} </div> ) } }
本文之外的一些兼容性问题,iOS 也有一些 bug 的,比如微信浏览器里收起键盘之后,页面不回弹,可参考文章。
References
The end.