背景

最近接到一个需求,其中一个功能点就是在一个 input 中输入表单内容,要求输入字段并展示搜索到的内容。本来是一个简单的需求,可是在跨平台开发的时候在 andorid 上却因为 andorid 键盘弹出影响了页面布局,导致最下面 fixed 布局的 footer 顶到了键盘上方,遮挡了 input 输入栏。

这种问题只出现在 android 上,因为 android 键盘弹出的时候会改变 H5 页面的视口大小,浏览器一旦发现视口变小(即高度减小),便会使 fixed 定位的 footer 往上移,直到 footer 在页面的最底部,而一旦 footer 顶上来了,就会遮挡住 input 输入栏,严重影响了用户体验。
这样的问题在 iOS 上不会发生,因为 iOS 上键盘弹起是属于不同的层级,不会影响页面布局。

大概在 andorid 上就是这样:

解决方案的探索

我们去网上搜索答案,发现很多人都遇到这种问题,汇总起来问题免不了解决方案大致分为以下几种:

  • 不要用 fixed 定位来定位 footer,利用相对定位并使用 css 的 cal 方法计算 footer 的位置
  • 利用 JavaScript 来操作 body 的高度,增加滚动条
  • 键盘弹起时,隐藏 footer。
  • 使用 scrollIntoView 方法,将 input 滚动到视口中

img

解决方案的初体验

通过搜索网上的解决方案并结合当前项目产品设计背景,发现既有方案都不适合我。
第一个方案 pass,因为当前的 body 是随着内容的不断增加高度不断增加的,而 footer 的要求只是要固定在视口的最下方,也就是说高度是不定的,但最小高度为 100vh(body: min-height: 100vh)。
第二个方案 pass too,高度是不定的,同样不能利用 JavaScript 设置 body 高度。
第三个方案 pass,第三个方案咋看还不错,但是我的页面不能判断键盘的弹起与否,只能通过 input 是否处于 focus 状态来调用 onFocus 事件来处理,但是一旦点击键盘上的收下按钮,input 同样处于 focus 状态,但是 footer 已经隐藏了,影响了体验,pass。
第四个方案,使用了但是无效。很难受。

最终方案的诞生

最终这个问题几经辗转,在和产品经理斡旋了三天三夜之后,产生了最终的解决方案,大概意思就是在 window 上绑定一个 resize 事件, 一旦 resize 事件发生之后,便将当前的 scrollTop 高度调整为总高度的 二分之一(input处于屏幕中间位置),这样 input 不仅可以完全展示在页面顶部,而且刚也不会被遮挡。
同理,在 iOS 中键盘弹起不会影响 window 触发 resize 事件,真可谓是完美解决方案。

页面为 React 编写,代码为:

1
2
3
4
5
6
7
8
useEffect(()=> {
// 实现所有浏览器的滚动高度为视口高度的一半
window.addEventListener("resize", () => {
window.pageYOffset = window.innerHeight / 2;
document.documentElement.scrollTop = window.innerHeight / 2;
document.body.scrollTop = window.innerHeight / 2;
});
}, [])

说明:
因为设置当前页面滚动高度存在兼容性问题,所以采用了三种方式设置滚动高度,具体可以看 stackoverflow 上的解释:
document.body.scrollTop Firefox returns 0 : ONLY JS

思考

因为在 react useEffect hooks 中添加了 resize 事件,所以用户不断 resize 视口,可能会导致性能问题,所以我们需要在渲染 html 中的 head 标签部分添加禁止用户缩放等 resize 事件:

1
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

这样的话整个项目只有在 input 中存在一个可以改变视口高度的位置,同时为了性能考虑可以考虑添加 节流或防抖 等功能。
done!