如何巧妙应对iOS键盘难题?
前言
写过移动端的同学或多或少都遇到过软键盘带来的各种各样的问题,最典型的就是输入框被软键盘遮挡、fixed元素失效等问题,并且这些问题在iOS上的表现让人难以接受。
webview的差异
在移动端上,我们的H5页面一般是运行在宿主APP提供的webview中,简单点理解,你其实可以把它当作浏览器,就是用来展现页面内容的。目前移动端主流系统分为Android与iOS,然而两者提供的webview容器也存在着诸多差异,今天我们就只探讨两者软键盘带来的影响。
首先,我们先来写个简单的页面布局:头部fixed+中间自适应+底部fixed

Android
事实上Android的表现并不会有太大问题,它只不过是在键盘弹起来之后把webview的高度减小了,变成了:原来的webview高度减去键盘的高度

这样的表现正是我们期待的,完全没有影响整个页面的布局
iOS
软键盘
在iOS 8.2 之后,iOS 唯一指定浏览器内核、Webkit 鼻祖 Safari 将 fixed 元素的布局基准区域从键盘上方的可见区域改成了键盘背后的整个视窗,也就是说此时的webview高度并不会发生变化,键盘是直接盖在webview上方的。
这样是为了在键盘弹起来之后,不用重新渲染页面,他们是方便了,但遭殃的是我们前端开发人员...
比如上面这个页面,我们看看iOS的表现是怎样的:

可以看到,iOS为了不让webview压缩,并且为了不让软键盘遮挡输入框,他们自作聪明地把webview整体往上移动,最大移动距离为软键盘的高度。
这样就导致我们的头部以及页面上半部分内容移动到了可视区之外,这个表现是难以接受的,至少头部应该还要在可视区。(这就会让我们误以为fixed失效,实际上它相对于webview的位置并没有变,只不过是webview发生了移动)
这个移动似乎没有逻辑,不信大家可以试试把输入框放到页面的各个位置,我发现只有输入框在最顶部,webview才不会发生上移,其它位置都或多或少的会产生移动。
还有一个问题就是,此时的webview是可以滑动的,那么就会出现有用户会将输入框滑动到键盘下方,想想这个体验也是难以接受的...

并且你会发现,在页面的上方与下方都多出了一个不论是 Viewport 还是 VisualViewport 都无法到达的白色衬底区域,我们可以尝试把页面所有元素背景都改成黑色再来看,会更加明显

看到这些奇奇怪怪的问题你心里作何感想??
所有问题产生的根本原因是:iOS为了不用在键盘弹起之后重新渲染页面,他们并没有去压缩webview容器的高度,而是对webview整体进行平移处理
软键盘监听
对于Android,我们通常可以通过监听resize事件来实现,但对于iOS,我们从上面了解到键盘弹起,iOS的webview高度并不会发生变化,所以也就触发不了resize事件。
在iOS中,可以通过focusin & focusout事件来进行监听
export const watchKeyBoard = (callback: (isShow: boolean) => void) => {
// IOS
if (isIOSByUA()) {
document.body.addEventListener('focusin', () => {
//软键盘弹出的事件处理
callback(true)
})
document.body.addEventListener('focusout', () => {
//软键盘收起的事件处理
callback(false)
})
} else {
// Android
const originalHeight =
document.documentElement.clientHeight || document.body.clientHeight
window.addEventListener('resize', () => {
const resizeHeight =
document.documentElement.clientHeight || document.body.clientHeight
if (resizeHeight - 0 < originalHeight - 0) {
// 键盘弹起事件
callback(true)
} else {
// 键盘收起事件
callback(false)
}
})
}
}
解决方案
了解完产生问题的原因,我们就可以来尝试着解决问题,但想要纯前端去解决这个问题,或多或少都会存在一些体验问题,也许你可以去推动你们的客户端同学来协助处理这个问题,只要让iOS的webview在键盘弹起时的表现与Android一致,就不会存在这些奇怪的问题了,但似乎他们处理起来也非常棘手...
模仿Android的处理
虽然我们改不了webview的高度,但我们可以改我们布局的高度,我们只需要将页面高度改为页面可视区的高度即可,如果页面内容有滚动交互的话,需要额外处理,要与webview的滚动隔离开。
VisualViewport
先来了解下这个API,它可以用来获取对应 window 的视觉视口
VisualViewport.offsetLeft:返回视觉视口的左边框到布局视口的左边框的 CSS 像素距离。VisualViewport.offsetTop:返回视觉视口的上边框到布局视口的上边框的 CSS 像素距离。VisualViewport.pageLeft:返回相对于初始的 viewport 属性的 X 轴坐标所对应的 CSS 像素数。VisualViewport.pageTop:返回相对于初始的 viewport 属性的 Y 轴坐标所对应的 CSS 像素数。VisualViewport.width:返回视觉视口的宽度所对应的 CSS 像素数。VisualViewport.height:返回视觉视口的高度所对应的 CSS 像素数。VisualViewport.scale:返回当前视觉视口所应用的缩放比例。
这里我们需要的就是这个VisualViewport.height,用来获取可视区的高度。
但需要注意的是,这个API最低只支持iOS13,ios13以下的使用window.innerHeight兜底
页面布局
整体布局采用flex布局,头部和底部也就不需要fixed来定位了,中间自适应撑满剩余高度,超长滚动

键盘打开计算高度重新布局
我们需要在键盘弹起后,计算可视区的高度,并将最外层容器高度赋值为可视区高度
watchKeyBoard((status) => {
setTimeout(() => {
console.log(
'status',
status ? '键盘打开' : '键盘关闭',
)
const container = document.getElementById('container')
if (status) {
container.style.height = `${
window.visualViewport.height || window.innerHeight
}px`
window.scrollTo(0, 0)
} else {
container.style.height = `100vh`
document.removeEventListener('touchmove', this.stopMove)
}
}, 100)
})
这样页面展示算是正常了

但是随之而来的是滚动问题

处理滚动
我们需要禁用全局的滚动,但对一些需要滚动的区域需要放开,比如中间的列表部分
if (utils.isIOSByUA()) {
watchKeyBoard((status) => {
setTimeout(() => {
console.log(
'status',
status ? '键盘打开' : '键盘关闭',
window.innerHeight,
)
const container = document.getElementById('container')
if (status) {
container.style.height = `${
window.visualViewport.height || window.innerHeight
}px`
window.scrollTo(0, 0)
document.addEventListener('touchmove', this.stopMove, {
passive: false,
})
document.addEventListener('touchend', this.scroll)
} else {
container.style.height = `100vh`
document.removeEventListener('touchmove', this.stopMove)
document.removeEventListener('touchend', this.scroll)
}
}, 100)
})
}
stopMove(e) {
// 排除可以滚动的区域
if (['content', 'keyboard_center'].includes(e.target?.className)) return
e.preventDefault()
}
scroll() {
window.scrollTo(0, 0)
}
完整体验如下

比起它原本带来的遮挡、滚动、fixed失效等体验,现在的体验算是可以接受的(这里所有的操作我们只需要在iOS上执行即可)
如何巧妙应对iOS键盘难题?的更多相关文章
- iOS开发小技巧--iOS键盘 inputView 和 inputAccessoryView
iOS键盘 inputView 和 inputAccessoryView 1.inputAccessoryView UITextFields和UITextViews有一个inputAccessoryV ...
- IOS - 键盘处理
iOS 发布了很多关于屏幕上键盘的通知.下面列出了这些通知的简要解释: UIKeyboardWillShowNotification 当键盘即将要显示的时候将会发出这个通知.这个通知包含了用户信息库, ...
- IOS键盘弹出、隐藏
IOS键盘 UIKeyboardFrameBeginUserInfoKey:动画开始前键盘的size UIKeyboardFrameEndUserInfoKey:动画结束后键盘的size - (voi ...
- 一行代码巧妙实现iOS返回button
一行代码巧妙实现iOS返回button: self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithImage:[ ...
- ios键盘弹起 body的高度拉长,页面底部空白问题。ios软键盘将页面抵到上面后,关闭软键盘页面不回弹的问题。
js 监听ios手机键盘弹起和收起的事件 /* js 监听ios手机键盘弹起和收起的事件 */ document.body.addEventListener('focusin', () => { ...
- iOS 键盘自适应(IQKeyboardManager)使用小结
IQKeyboardManager Github地址 经常在开发一个应用程序,我们遇到了一个问题,iPhone的键盘上滑覆盖的UITextField / UITextView.IQKeyboardMa ...
- iOS键盘中英文切换键盘高度获取通知方法
iOS键盘中英文切换键盘高度获取通知方法, 有需要的朋友可以参考下. 注册通知 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppea ...
- iOS 键盘遮挡输入框万能解决方案(多个输入框)
效果图如下: 思路分析: 代码: 知识点: 问题: 效果图如下: 思路分析: 当我们有很多输入框时,有时候键盘弹出来会遮挡着输入框.我们需要获取输入框和键盘相对于最外层视图的位置来判断是否遮挡,如果遮 ...
- h5页面ios键盘弹出收起后页面底部留白问题
<input placeholder="验证码" type="tel" v-model="verify" maxlength=&quo ...
- iOS键盘输入屏幕上移
在iOS开法中经常会遇到键盘遮挡屏幕的事情(比如输入账号密码验证码等等),就使得原本都不大的屏幕直接占了一半甚至更多的位置,这倒无所谓,关键是挡住了下面的按钮.这样的话按钮的事件也就触发不了,最好的解 ...
随机推荐
- 【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
问题描述 如何查看App Service Java堆栈JVM相关的参数默认配置值? 问题解答 可以通过App Service的高级管理工具(kudu:)来查看JVM的相关参数,使用命令:java -X ...
- hadoop集群启动成功但进入web50070管理界面显示DataNode为0与集群运行,结果全为0的问题总结
Hadoop完全分布式出现DataNode为0的解决方案 问题:在配置好Hadoop后,jps命令下看见Hadoop服务已经启动,namenode和datanode都已经启动,但进入Hadoop界面还 ...
- 3、mysql定位低效率执行SQL
可以通过以下两种方式定位执行效率较低的 SQL 语句. 慢查询日志 : 通过慢查询日志定位那些执行效率较低的 SQL 语句,用--log-slow-queries[=file_name]选项启动时,m ...
- CUDA指针数组Kernel函数
技术背景 在前面的一篇文章中,我们介绍了在C++中使用指针数组的方式实现的一个不规则的二维数组.那么如果我们希望可以在CUDA中也能够使用到这种类似形式的不规则的数组,有没有办法可以直接实现呢?可能过 ...
- YAML 语法简介与 C# 操作示例
〇.简介 YAML(Yet Another Markup Language)另一种标记语言. YAML 是一种较为人性化的数据序列化语言,可以配合目前大多数编程语言使用.YAML 的语法比较简洁直观, ...
- [.Net]使用Soa库+Abp搭建微服务项目框架(一):Abp与DDD相关知识回顾
在企业中大型项目中,随着业务的不断拓展,项目发展到一定程度,需要寻求项目的各模块解耦,独立成为微服务.如何实现呢? 首先我们先来简单回顾一下Abp框架怎样实现(DDD)领域驱动设计的,Abp框架的 ...
- Java中枚举配合switch语句用法-2022新项目
一.业务场景 项目开发中经常会遇到多条件判断的情况,如果判断条件少的话使用if/elseif/else还比较好处理,如果判断条件多的话,则在使用这种语句就不太合适. 如果是自定义的一些内容,比如不同的 ...
- translate speaker 翻译朗读者API - vscode 插件推荐 单词发音
translate speaker 翻译朗读者API - vscode 插件推荐 单词发音 有个小bug,就是发音发两次,改个配置就好了. "translateSpeaker.mode&qu ...
- Redis持久化之RDB(Redis DataBase) 和 AOF(Append Only File)
Redis提供了两种持久化方式:RDB(Redis DataBase)和AOF(Append Only File). 1. RDB持久化: RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁 ...
- Linux 服务器Python后台运行服务(ssh断开不退出)
壹: 最近用python搭建一个物联网数据存储的微服务,部署到ubuntu上去,所以,python后台运行是一个必不可少的环节. 贰: 这个只需要是一个命令即可: 命令1(记录所有日志): nohup ...