必须分层兜底:iOS 16.4+ 优先用 env(keyboard-inset-bottom) 配合 viewport-fit=cover 和 @supports;次选 position: sticky(需 min-height: 100vh 且无 transform);最后用 focusin/blur 动态切换 absolute 定位并计算 visualViewport.height。

不能只靠 bottom: 0 或 100vh,必须组合 env(keyboard-inset-bottom)、position: sticky 和 JS 动态 fallback 才能稳定避让。
用 env(keyboard-inset-bottom) 做 iOS 原生避让(16.4+)
这是目前最干净的纯 CSS 方案,但仅限 iOS Safari 16.4 及以上生效,且必须满足三个前提:
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">必须存在,否则变量不触发 - 写法必须是
bottom: env(keyboard-inset-bottom, 0px),0px是 fallback,漏掉会导致键盘收起后按钮悬空 - 必须用
@supports (bottom: env(keyboard-inset-bottom))包裹,否则旧版 Safari 会整条规则忽略甚至解析失败 - 安卓完全不支持该变量,别把它当通用解法——它只是 iOS 的“加速键”,不是终点
改用 position: sticky 替代 fixed(推荐首选)
如果你的底部栏本就紧贴内容流末端(如聊天输入框、评论提交区),sticky 是副作用最少、兼容性已足够好的选择:
- 父容器需设
min-height: 100vh且允许内部滚动,不能加overflow: hidden或transform,否则sticky失效 - 底部元素写
position: sticky; bottom: 0;,它不脱离文档流,天然不受视觉视口压缩影响 - iOS 15.4+、Android Chrome 90+ 均支持;老版本可渐进降级为
absolute+ JS 计算 - 比
absolute更少踩坑:不用管setTimeout时机、不依赖focusin精确坐标、也不怕父容器transform干扰
监听 focusin/blur 动态切 absolute 定位(兼容兜底)
这是目前覆盖机型最广的 JS 方案,但不是简单换 position,关键在锚点控制:
立即学习“前端免费学习笔记(深入)”;
- 根容器必须设
position: relative+min-height: 100vh(不能只写height: 100%,否则键盘弹出时塌陷) - 底部元素加
width: 100%,否则横屏或缩放后偏移 -
focusin后必须用setTimeout(() => { ... }, 0)等布局更新完成,再读input.getBoundingClientRect().bottom - 计算
bottom值用window.innerHeight - inputRect.bottom,不是top,也不是写死数值 -
blur后立刻恢复position: fixed; bottom: 0,并用el.style.removeProperty('bottom')清除内联样式,否则滚动时消失
为什么 resize 和 media query 都靠不住
它们根本捕获不到键盘弹出的真实状态:
-
window.addEventListener('resize')在 iOS Safari 中触发率低于 20%,且常只在键盘收起时触发一次,漏掉弹出瞬间 -
@media (max-height: ...)依赖设备初始参数,键盘压缩的是visualViewport.height,不触发媒体查询重算 -
document.documentElement.scrollHeight和window.innerHeight在键盘弹出期间可能未及时更新,唯一可信的是visualViewport.height(但低版本不支持) - 真正可靠的入口只有
focusin(不是focus,冒泡更稳)和blur,且必须监听所有可聚焦元素:input、textarea、[contenteditable]
真正麻烦的不是怎么写 CSS,而是不同 WebView 对「键盘是否改变视口」的判断逻辑不一致——iOS 冻结 layout viewport、Android 直接压缩 innerHeight、微信内置浏览器甚至伪造高度。所以没有银弹,只有分层兜底:优先用 env(),退化用 sticky,最后靠 absolute + focusin 精控。任何跳过这三层的方案,在真实机型上都大概率穿帮。


















