position: absolute替代是目前兼容性最广、落地最稳的解法,需满足根容器设position: relative和min-height: 100vh、底部元素加width: 100%、监听focusin/blur动态计算bottom值三个硬条件。

这不是你 position: fixed 写错了,而是浏览器把 fixed 元素锚定在「视觉视口(visual viewport)」上,而软键盘弹出会压缩或上推这个视口——但 fixed 元素的定位坐标不会重算,结果就飘在键盘上方空白处。
fixed 锚定的是 visual viewport,不是 layout viewport
iOS 和多数安卓 WebView 把软键盘当成覆盖层,只缩 visualViewport.height,却不触发 layout viewport 重排。bottom: 0、100vh、甚至 @media (max-height: ...) 都基于初始或 layout 视口,根本感知不到 visual viewport 的动态变化。
-
document.documentElement.clientHeight通常不变,它反映的是 layout viewport,和用户实际看到的区域脱节 -
window.innerHeight在键盘弹出后会变小,但 fixed 元素渲染时仍用旧值计算位置 - 哪怕加了
viewport-fit=cover,也只影响安全区,不改变 fixed 的锚点逻辑
resize 事件在 iOS 和安卓上都不可靠
你以为监听 resize 就能捕获键盘弹出?现实是:
- iOS Safari 中
resize延迟严重,常在键盘完全展开后 300–500ms 才触发,且可能连发多次 - 安卓部分 WebView 根本不触发
resize,或者只在键盘收起时才触发一次 -
focusin又太早:触发时键盘还没弹出来,getBoundingClientRect()返回的是旧坐标
env(keyboard-inset-bottom) 不是万能解
这个 CSS 环境变量确实干净,但它只在 iOS Safari 16.4+ 上生效,安卓全系不支持,旧版 iOS 会忽略整条规则甚至导致解析失败。
- 必须用
@supports (bottom: env(keyboard-inset-bottom))包裹,否则降级失效 - 不能和
svh混用,比如bottom: calc(0px + 100svh)是无效写法 - 即使启用了,iOS 键盘收起后
window.innerHeight也不立即恢复,需手动判断阈值并清理状态
真正稳定的方案永远是组合
用 visualViewport.height 做主监听(现代浏览器),fallback 到 focusin/blur + setTimeout(() => {}, 0) 动态切 position: absolute,再用 padding-bottom 或 margin-bottom 做兜底。任何只改一个 CSS 属性或只绑一个事件的方案,在真实机型上都大概率穿帮。
最麻烦的不是怎么写 CSS,而是怎么让 JS 在不同机型、不同 WebView(比如微信内置、QQ 浏览器、三星 Internet)里保持行为一致——尤其是 focusin 时机、DOM 更新节奏、滚动锚点恢复这几个点,稍有偏差就会错位或抖动。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











