移动端 fixed 元素因输入法弹起错位,本质是锚定视觉视口导致;iOS 16.4+ 可用 env(keyboard-inset-bottom) 零 JS 避让,需 viewport-fit=cover 并 @supports 降级;安卓不支持,须结合 visualViewport 监听、focusin/blur 延迟计算或改用 relative 容器内嵌按钮。

移动端 position: fixed 元素在输入法弹起时错位,不是你 CSS 写错了,而是浏览器把 fixed 锚定在「视觉视口(visual viewport)」上,而输入法会压缩或冻结这个视口——但元素定位值不会重算,结果就飘在键盘上方。纯 CSS 无法感知键盘状态,必须组合环境变量、定位切换和事件监听才能稳定避让。
用 env(keyboard-inset-bottom) 做 iOS 原生避让(16.4+)
这是目前最干净的方案,零 JS,但仅限 iOS Safari 16.4 及以上支持:
- 必须在
<meta>中启用适配:<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> - 底部元素写法:
bottom: env(keyboard-inset-bottom, 0px);—— 键盘弹出时自动上浮,收起后回退到0px - 必须用
@supports降级,否则旧版 Safari 会忽略整条规则:@supports (bottom: env(keyboard-inset-bottom)) { .btn-footer { bottom: env(keyboard-inset-bottom, 0px); } } - 安卓完全不支持该变量,不能只靠它;也别和
svh混用,比如bottom: calc(0px + 100svh)是错的——svh是高度单位,env(keyboard-inset-bottom)是偏移量,语义不同
用 position: absolute 替代 fixed(兼容性最强)
直接改 position: absolute 会同样错位,关键在于祖先容器是否“稳”:
- 根容器(如
#app或body)必须设position: relative和min-height: 100vh(不能只写height: 100%,否则键盘弹出时塌陷) - 底部区域要加
width: 100%,否则横屏或缩放后可能偏移 - 必须监听
focusin和blur,不能依赖resize——iOS 上resize触发率低于 20%,安卓延迟严重,且无法捕获键盘弹起瞬间 - 在
focusin回调里加setTimeout(() => { }, 0),确保 DOM 重排完成后再取坐标 - 用
window.innerHeight - inputRect.bottom计算bottom偏移,而非写死数值 -
blur后立刻恢复position: fixed; bottom: 0,并调用el.style.removeProperty('top')和el.style.removeProperty('bottom')清除残留 inline 样式
监听 visualViewport 获取真实可视高度(精度最高)
这是目前最准、最轻量的方案,能直接拿到键盘弹出后用户真正看到的高度(visualViewport.height),不依赖抖动的 resize 或不可靠的 window.innerHeight:
立即学习“前端免费学习笔记(深入)”;
- 页面加载时先存下无键盘状态的基准高度:
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+ 支持;老版本需降级,不能只靠它
- fallback 到
focusin/blur+ 延迟高度探测:用setTimeout或requestAnimationFrame延迟读取window.innerHeight,确保拿到压缩后的值
什么时候该放弃 fixed/absolute,直接嵌入输入框?
如果底部按钮语义上就是输入操作的一部分(比如聊天页的「发送」、搜索页的「语音」),最稳方案是彻底脱离视口定位思维:
- 把按钮作为
input的兄弟节点,放在同一个position: relative容器里 - 用
display: flex; justify-content: space-between或align-items: center控制对齐 - 不再依赖视口定位,自然规避所有键盘导致的高度误判
- 避免对
body或html设置overflow: hidden,这可能阻断键盘收起时的正常滚动恢复
最容易被忽略的是:键盘收起后,iOS 微信 WebView 常卡在高位,必须用 setTimeout(() => { window.scrollTo(0, 0) }, 100) 强制恢复滚动锚点——太短无效,太长又影响体验。


















