如何巧妙应对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 服务总线】Azure门户获取ARM模板,修改Service Bus的TLS版本
问题描述 在Azure中创建Sverice Bus服务后,如果想修改服务的TLS版本,是否有办法呢? 问题解答 通过Service Bus的ARM模板,修改属性值中的 minimumTlsVersio ...
- 【Azure API 管理】APIM关闭开发者门户的办法
问题描述 APIM默认提供了开发者门户,可以让用户体验如何来调用接口.但如果不想开发这个功能的情况下,是否有办法关闭呢? 问题解答 答案是:开发人员门户是没有办法关闭的.但是作为另一种的代替方案,如自 ...
- 【Azure Redis 缓存】VM 里的 Redis 能直接迁移到 Azure Cache for Redis ? 需要改动代码吗?
问题描述 原来部署在VM 里的 Redis 能直接迁移到 Azure Cache for Redis? 需要改动代码吗? 问题解答 以上问题需要从两个方面来解答. 第一:VM中Redis的数据转移到 ...
- Frechet Inception Distance
(以下内容重新更新,主要讲解FID的意义,为何FID能够表示生成图像的多样性和质量,为什么FID越小,则图像多样性越好,质量也越好) 在计算FID中我们也同样使用inception network网络 ...
- Java 韩顺平老师的课,记的(前6章)笔记
https://www.bilibili.com/video/BV1fh411y7R8/?p=110&spm_id_from=333.880.my_history.page.click& ...
- .Net之配置文件自定义
前文讲获取配置文件内容的时候,是获取默认的appsettings.json配置文件的配置,下面说明下如何进行自定义配置文件获取 1. Json Provider 1.1 构建独立的IConfigura ...
- 排查 dotNET Core 程序内存暴涨的问题
0. 问题 新版本上线之后,发现内存猛涨,入站流量猛增,不清楚具体原因,部分接口提示 OOM 异常,随后 Pod 直接崩溃无限重启. 1. 准备 Pod 已经接入了 NewRelic 和 Graylo ...
- ARM的无线ble IP Cordio-B50 stack and profiles简析
一 简介 人家英文写的很清楚,我就不蹩脚额翻译了. Cordio-B50 stack is designed specifically for Bluetooth low energy single- ...
- Android 桌面小组件使用
原文: Android 桌面小组件使用-Stars-One的杂货小窝 借助公司上的几个项目,算是学习了Android桌面小组件的用法,记下踩坑记录 基本步骤 1.创建小组件布局 这里需要注意的事,小组 ...
- Java中float和double的精度
比较基本类型double和float和某一个数是否相等的时候,不要用==或>=或<=,因为double和float都是有精度问题的,计算机只能保存一定位数的小数,这就会存在精度问题. 下面 ...