position: fixed元素在输入法弹出时错位,根本原因是其锚定视觉视口,而iOS冻结layout viewport上推页面、Android压缩window.innerHeight却不触发重算;bottom: 0和100vh无效因值静态固化,resize事件不可靠,env(keyboard-inset-bottom)仅限iOS 16.4+且安卓不支持,需以focusin为入口组合处理。

不是 CSS 写错了,而是 position: fixed 锚定的是「视觉视口(visual viewport)」,而输入法弹出时,这个视口在 iOS 和 Android 上的处理方式完全不同——iOS 会冻结 layout viewport 高度但上推页面,Android 则直接压缩 window.innerHeight,但都不触发 fixed 元素的位置重算。结果就是按钮飘在键盘上方、被顶到屏幕中间,甚至完全移出可视区。
为什么 bottom: 0 + 100vh 依然无效
这些值在页面加载时就被计算并固化,不会随键盘动态更新:
- 100vh 始终等于设备物理屏幕高度,不是当前可用视口高度
- bottom: 0 是相对于初始 visual viewport 底部,键盘弹出后该“底部”已上移,但元素没重定位
- @media 查询不响应运行时视口变化,写 @media (max-height: 400px) 不会重新匹配
为什么不能只靠 window.onresize 监听
resize 事件在移动端极其不可靠:
- iOS Safari 中触发率极低,常完全不触发
- Android Chrome 可能延迟 200–300ms 才派发,UI 错乱早已发生
- 微信 WebView 中实测触发率低于 20%
- 在回调里直接改 style.bottom 会强制同步重排,滚动卡顿明显
- 它无法区分横竖屏切换和键盘弹起,容易误判
为什么 env(keyboard-inset-bottom) 不能单独用
这个 CSS 环境变量虽干净,但有硬限制:
- 仅 iOS Safari 16.4+ 支持,安卓 WebView 全系不识别
- 必须配合 <meta name="viewport" content="viewport-fit=cover"> 才生效
- 旧版 Safari 遇到未识别的 env() 会整条规则失效,必须用 @supports 包裹降级
- 它只提供偏移量,不能解决键盘收起后 window.innerHeight 不恢复、滚动锚点卡住等问题
真正稳定的方案永远是组合动作:用 focusin 作为唯一可靠入口点,约束根容器高度,动态切换定位状态,并在 blur 后手动清理样式和恢复锚点——哪怕用了 visualViewport 或 env(),这些清理步骤也一个都不能跳过。


















