单纯用position: fixed无法避免软键盘遮挡,因iOS/安卓将fixed锚定在动态压缩的visual viewport上且不重算定位值;唯一纯CSS解法是env(keyboard-inset-bottom)(仅iOS 16.4+),需viewport-fit=cover与@supports包裹,安卓及旧系统必须JS切换absolute或padding-bottom兜底。

单纯用 position: fixed 无法避免软键盘遮挡——这不是写法问题,而是浏览器行为本身不支持。 iOS 和安卓 WebView 对 fixed 的锚定基准(visual viewport)在键盘弹出时会动态压缩或冻结,但 CSS 不会重算 bottom 或 top 值。所有靠 100vh、@media、z-index 或硬写 bottom: 60px 的尝试,都会在真实机型上失效。
用 env(keyboard-inset-bottom) 让 iOS 自动避让(仅限 Safari 16.4+)
这是目前唯一纯 CSS、无需 JS 的有效解法,但只对 iOS 生效:
-
bottom: env(keyboard-inset-bottom, 0px)会在键盘弹出时自动设为当前键盘高度,收起时回退到0px - 必须搭配
<meta name="viewport" content="viewport-fit=cover">,否则不触发 - 必须用
@supports (bottom: env(keyboard-inset-bottom))包裹,否则旧版 Safari 会跳过整条规则甚至解析失败 - 不能和
svh混用,例如bottom: calc(100svh + env(keyboard-inset-bottom))是错的——svh是高度单位,env()是偏移量,语义不同
给容器加 padding-bottom 预留空间(全平台兼容)
这是最稳的兜底方案,适用于所有浏览器,包括老安卓 WebView:
- 在最外层内容容器(如
#app)上设padding-bottom: 60px,并确保box-sizing: border-box - 不要直接作用于
body加margin-bottom,它不阻止遮挡,还可能触发外边距合并 - 若底部元素高度不固定,可用 CSS 变量配合 JS 动态更新:
document.documentElement.style.setProperty('--kb-height', '60px'),再在 CSS 中用padding-bottom: var(--kb-height) - 响应式场景慎用
calc(100vh - 100% + 60px),部分安卓 WebView 不识别该写法
监听 focusin/blur 切换 position: absolute(JS 必须项)
安卓完全不支持 env(keyboard-inset-bottom),iOS 旧版本也不支持,此时必须用 JS 动态切换定位方式:
立即学习“前端免费学习笔记(深入)”;
- 监听
focusin(不是focus),并在回调里加setTimeout(() => {}, 0),确保 DOM 重排完成后再读坐标 - 计算方式是
window.innerHeight - inputRect.bottom,不是input.offsetTop(relative 容器下不可靠) - 多个输入框共用同一底部栏时,需用全局变量记录当前激活的
input,避免重复切换或漏恢复 -
blur后立刻恢复position: fixed和原始bottom: 0,并调用el.style.removeProperty('top')和el.style.removeProperty('bottom')清理 inline 样式 - 所有计算前先确认
document.activeElement === inputEl,防止异步焦点转移导致状态错位
为什么别浪费时间在 resize 上
resize 在键盘场景中是幻觉:
- iOS 上触发率低于 20%,且常延迟数百毫秒;安卓 WebView 行为碎片化,有些根本不触发
- 键盘弹出瞬间,
window.innerHeight还没变,resize就已执行,拿到的是错误高度 - 它无法捕获键盘“开始弹出”这一关键帧,只能作为
focusin/blur的辅助兜底,不能当主入口 - 若页面用了
transform、overflow: hidden或fixed父容器,getBoundingClientRect()和scrollIntoView()都可能静默失效,且无报错提示
真正复杂的地方不在代码行数,而在于三者叠加:iOS 用 env(),安卓靠 absolute 切换,所有平台都依赖 padding-bottom 预留 + focusin/blur 状态同步。漏掉任意一环,比如忘了清空 inline 样式、没加 viewport-fit=cover、或在 transform 容器里调 getBoundingClientRect(),都会导致按钮突然消失或卡在半空。


















