fixed弹窗软键盘错位是因ios/安卓webview不响应视口变化,fixed元素仍按初始window.innerheight计算位置;应优先用env(keyboard-inset-bottom)配合viewport-fit=cover实现ios原生避让,安卓需降级为absolute+动态bottom计算,并处理键盘收起延迟、focusin时机及scrollto节流问题。

为什么 fixed 弹窗在软键盘弹出时会错位
不是你 position: fixed 写错了,而是 iOS 和多数安卓 WebView 不把软键盘当作「视口变化」来响应。它们压缩的是 visualViewport.height,但 fixed 元素仍按初始 window.innerHeight 计算位置——结果弹窗卡在键盘上方、被顶出屏幕,甚至完全不可见。bottom: 0、100vh、z-index 都无效,因为这些值不会随键盘动态更新。
用 env(keyboard-inset-bottom) 做 iOS 原生避让(16.4+)
这是目前最干净的方案,零 JS,但仅限 iOS Safari 16.4+ 支持:
- 必须加
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> - 弹窗样式写成:
bottom: env(keyboard-inset-bottom, 0px),不能只写env(keyboard-inset-bottom),否则键盘收起后悬空 - 必须用
@supports (bottom: env(keyboard-inset-bottom))包裹,否则旧版 Safari 会忽略整条规则甚至解析失败 - 安卓完全不支持,不能作为唯一方案
用 position: absolute 模拟 fixed(兼容性最广)
关键不在改定位方式,而在控制锚点容器和状态同步:
- 根容器(如
#app)必须设position: relative+min-height: 100vh(不能只写height: 100vh,否则键盘弹出时塌陷) - 弹窗元素加
width: 100%,避免横屏或缩放后偏移 - 监听
focusin(不是focus)到input、textarea、[contenteditable],并在回调里加setTimeout(() => { }, 0)确保 DOM 重排完成后再读getBoundingClientRect().bottom - 计算
bottom值用window.innerHeight - inputRect.bottom,不是写死数值,也不是inputRect.top -
blur后必须立刻恢复position: fixed和bottom: 0,否则滚动时弹窗会消失
容易被忽略的三个硬伤
所有方案都卡在这三点上,不处理就会抖动、延迟或失效:
- iOS 键盘收起后
window.innerHeight不立即恢复,需用visualViewport.height或延时检测 -
focusin触发早于键盘完全展开,直接读坐标是旧值,setTimeout(() => {}, 0)是底线,不是可选项 - Safari 对
window.scrollTo(0, 0)有节流策略,强制滚动需配合requestAnimationFrame或双层setTimeout
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











