如何巧妙应对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开法中经常会遇到键盘遮挡屏幕的事情(比如输入账号密码验证码等等),就使得原本都不大的屏幕直接占了一半甚至更多的位置,这倒无所谓,关键是挡住了下面的按钮.这样的话按钮的事件也就触发不了,最好的解 ...
随机推荐
- 黑马python基础课的一些题
1, 打印5行小星星 思路: 可以用1个星星乘以行数:还可以循环嵌套,外层循环控制行数,内层循环控制每一行应该输出多少个小星星,比如,第一行输出1个,第二行输出2个,内层循环可以当成列,只不过这个列要 ...
- C++ STL 容器 forward_list类型
C++ STL 容器 forward_list类型 介绍 std::forward_list 是 C++ 标准模板库 (STL) 中的一个单向链表容器.与 std::list 不同,std::forw ...
- ArrayList继承了AbstractList为何还要实现List接口
ArrayList继承了AbstractList为何还要实现List接口? 相关的问题: Vector既然继承了AbstractList为啥还要实现List接口 HashMap继承了AbstractM ...
- 上来就对标 20k Star 的开源项目,是自不量力还是后起之秀?
先来一段紧箍咒:nvm.fvm.gvm.sdkman.fnm.n.g.rvm.jenv.phpbrew.rustup.swiftenv.pyenv.rbenv... 这些都是用来解决编程语言多版本管理 ...
- 闭关修炼180天----手写迷你版的tomcat-Minicat
手写迷你版的tomcat-Minicat 小谈Tomcat Tomcat请求处理⼤致过程 Tomcat是⼀个Http服务器(能够接收并且处理http请求,所以tomcat是⼀个http服务器) 我们使 ...
- Git修改最近一次提交的日志信息
一.问题由来 当前自己所在的项目组中,每次发完一个版本后,就需要创建个人新版本的git提交凭证,其实就是系统自动 生成的一串编码,如果没有这个凭证,代码是提交不了的,这是公司制定的开发规范之一.这两天 ...
- Kettle实战视频教程
kettle实战视频教程 欢迎关注笔者的公众号: java大师, 每日推送java.kettle运维等领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!!个人网站: http://w ...
- nginx和tomcat 反向代理 部署实例 直接运行
1 前言 1.1 目的 为了正确的部署"ngix+memcached"特编写此部署手册,使安装人员可以通过部署手册知道如何部署系统,也为需要安装该系统的安装人员正确.快速的部署本系 ...
- My97DatePicker设置默认时间
<%@ page language="java" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC ...
- RunOnWeb - 创建新协议,支持html调用本地可执行文件,支持浏览器互相调用
浏览器调用 exe ? Yes! 谷歌 Chrome 启动微软 Edge ? Yes! RunOnWeb 协议 创建新协议,支持html调用本地可执行文件,支持浏览器互相调用 [最新版 ...