fixed元素锚定视觉视口,软键盘压缩该视口但不触发重排,导致定位错位;resize事件延迟或缺失、env(keyboard-inset-bottom)兼容性差,稳定方案需组合visualViewport监听与fallback机制。
不是你 position: fixed 写错了,而是浏览器把 fixed 元素锚定在「视觉视口(visual viewport)」上,而软键盘弹出会压缩这个视口——但 fixed 的计算值不会重算,结果元素就飘在键盘上方空白处。
iOS 和多数安卓 WebView 把软键盘视为“覆盖层”,只缩 visual viewport.height,却不触发 layout viewport 重排。bottom: 0、100vh、甚至 @media (max-height: ...) 都基于初始或 layout 视口,根本感知不到 visual viewport 的动态压缩。
window.innerHeight 在键盘弹出后会变小,但 fixed 元素的定位坐标仍按旧值渲染document.documentElement.clientHeight 通常不变,它反映的是 layout viewport,和用户实际看到的区域脱节viewport-fit=cover,也只影响安全区,不改变 fixed 的锚点逻辑你以为监听 resize 就能捕获键盘弹出?现实是:
resize 延迟严重,常在键盘完全展开后 300–500ms 才触发,且可能连发多次resize,或者只在键盘收起时才触发一次focusin 触发时键盘还没弹出来,getBoundingClientRect() 返回的是旧坐标,直接读会错位这个 CSS 环境变量确实干净,但它只在 iOS Safari 16.4+ 上生效,安卓全系不支持,旧版 iOS 会忽略整条规则甚至导致解析失败。
@supports (bottom: env(keyboard-inset-bottom)) 包裹,否则降级失效svh 混用,比如 bottom: calc(0px + 100svh) 是无效写法window.innerHeight 也不立即恢复,需手动判断阈值并清理状态真正稳定的方案永远是组合:用 visualViewport.height 做主监听(现代浏览器),fallback 到 focusin/blur + setTimeout(() => {}, 0) 动态切 position: absolute,再用 padding-bottom 或 margin-bottom 做兜底。任何只改一个 CSS 属性或只绑一个事件的方案,在真实机型上都大概率穿帮。