fixed元素在移动端软键盘弹出时因锚定视觉视口底部而错位;focusin/blur是唯一可靠监听方式;absolute替代需满足根容器relative+min-height、宽度100%、动态切换;env(keyboard-inset-bottom)仅iOS 16.4+支持且需viewport-fit=cover;visualViewport API最精准,但需兼容降级。

fixed 在移动端软键盘唤起后“失效”,不是写错了,而是它被锚定在视觉视口(visual viewport)底部,而键盘弹出会压缩或上推该视口——但 fixed 元素不会重算位置,结果就是按钮飘在半空、卡在键盘上方,甚至被顶出屏幕。
为什么 focusin/blur 是唯一可靠监听入口
resize 事件在 iOS Safari(尤其是 15.4+)中触发率低于 20%,安卓行为也不统一,且常只在键盘收起时触发一次,完全漏掉弹出瞬间。focusin(注意不是 focus)冒泡可靠,能捕获任意可聚焦元素获得焦点的时刻:
• 必须监听 input、textarea 和 [contenteditable]
• 监听后立刻校验 document.activeElement === inputEl,防止异步焦点转移导致状态错位
• blur 回调里必须立刻恢复原始定位,否则滚动时元素直接消失
用 position: absolute 替代 fixed 的三个硬条件
只改 position: absolute 会同样错位,因为 absolute 的基准是最近的 position: relative 祖先容器,而这个容器本身可能塌陷:
• 根容器(如 #app 或 body)必须设 position: relative 和 min-height: 100vh(不能只写 height: 100%,否则键盘弹出时塌陷)
• 底部元素必须加 width: 100%,否则横屏或缩放后偏移
• 必须监听 focusin/blur 动态切换定位状态,不能靠 CSS 静态声明
env(keyboard-inset-bottom) 能用但有致命限制
bottom: env(keyboard-inset-bottom, 0px) 是 iOS Safari 16.4+ 原生支持的最优解,键盘弹出自动上浮,收起回退。但它不是万能:
• 安卓 WebView 完全不支持,旧版 Safari(如 15.3)也不识别
• 必须搭配 @supports (bottom: env(keyboard-inset-bottom)) 降级,否则整条规则被忽略
• 必须写 fallback:bottom: env(keyboard-inset-bottom, 0px),不能只写 env(keyboard-inset-bottom),否则键盘收起后按钮悬空
• 必须在 <meta> 中启用适配:<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
立即学习“前端免费学习笔记(深入)”;
visualViewport API 是目前最准的 JS 方案
相比抖动的 resize 或不可靠的 window.innerHeight,visualViewport 能直接读取用户当前真正看到的高度:
• 页面加载时存基准值:const baseHeight = visualViewport.height
• 监听 visualViewport.addEventListener('resize', () => { ... })
• 计算差值:const kbHeight = baseHeight - visualViewport.height
• 用 btn.style.transform = `translateY(${kbHeight}px)` 调整位置,比改 bottom 更少触发重排
• 注意兼容性:iOS Safari 15.4+、Chrome 61+ 支持;老版本需降级到 focusin + setTimeout(() => {}, 0) 延迟读取
真正容易被忽略的是:所有坐标计算前,必须确认 document.activeElement 仍指向目标输入框;一旦页面存在 transform、overflow: hidden 或 position: fixed 父级,getBoundingClientRect() 可能返回错误值,且不报错。


















