position: fixed 在软键盘弹出时“滑动”是因为其锚定视觉视口底部,而键盘弹出会压缩或上推该视口,导致元素错位;需通过 focusin/blur 监听 + absolute 定位 + 动态 bottom 计算 + env(keyboard-inset-bottom) 降级方案协同解决。

为什么position: fixed在软键盘弹出时会“滑动”
这不是动画或过渡导致的,而是浏览器把 position: fixed 锚定在「视觉视口(visual viewport)」底部,而软键盘弹出时,这个视口高度被压缩(Android)或上推冻结(iOS),但 fixed 元素不重算位置——结果就是按钮从底部飘到屏幕中间、卡在半空,甚至被顶出可视区。写 bottom: 0 或用 100vh 都无效,因为这些值不随键盘动态更新。
focusin/blur 是唯一可靠入口点,别碰 resize
window.addEventListener('resize') 在 iOS Safari(尤其是 15.4+)中触发率低于 20%,安卓行为也不统一,且常只在键盘收起时触发一次,完全漏掉弹出瞬间。真正该监听的是 focusin(不是 focus,它冒泡更可靠)和 blur:
- 必须监听所有可聚焦元素:
input、textarea、[contenteditable] - 别只监听
input:漏掉textarea就等于漏掉一半场景 - 监听后立刻检查
document.activeElement === inputEl,防止异步焦点转移导致状态错位 -
blur回调里必须立刻恢复原始定位,否则滚动时元素直接消失
用 position: absolute 替代 fixed 的硬条件
只改 position: absolute 会同样错位。真正起效需同时满足三个约束:
- 根容器(如
#app或body)设position: relative和min-height: 100vh(不能只写height: 100%,否则键盘弹出时塌陷) - 底部区域加
width: 100%,否则横屏或缩放后偏移 - 聚焦时切换为
position: absolute,并用setTimeout(() => {}, 0)延迟读取getBoundingClientRect()—— 否则坐标仍是弹出前的旧值 - 计算
bottom值用window.innerHeight - inputRect.bottom,不是写死数值 -
blur后清除内联样式:el.style.removeProperty('top')和el.style.removeProperty('bottom'),再恢复position: fixed; bottom: 0
env(keyboard-inset-bottom) 能用但不能全靠它
bottom: env(keyboard-inset-bottom, 0px) 是 iOS Safari 16.4+ 原生支持的最优解,键盘弹出时自动上浮,收起回退。但它有致命限制:
立即学习“前端免费学习笔记(深入)”;
- 安卓完全不支持,旧版 Safari(如 15.3)也不识别
- 必须搭配
@supports (bottom: env(keyboard-inset-bottom))降级,否则整条规则被忽略 - 必须写 fallback:
bottom: env(keyboard-inset-bottom, 0px),不能只写env(keyboard-inset-bottom),否则键盘收起后按钮悬空 - 必须加
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
最易被忽略的是:所有方案都依赖 document.activeElement 状态同步,一旦页面用了 transform、overflow: hidden 或 fixed 父容器,getBoundingClientRect() 和 scrollIntoView() 都可能静默失效,且无报错提示。


















