fixed元素错位主因是软键盘弹出导致visualViewport高度塌缩,而fixed仍锚定原始视口底部;focusin时需setTimeout延后取getBoundingClientRect()确保重排完成,且blur后必须清除inline bottom样式并恢复定位。

不是 fixed 本身在跳,是它下面的内容在动——输入框聚焦触发软键盘弹出,浏览器压缩 visualViewport.height 或上推页面,但 fixed 元素仍锚定在原始视口底部,导致视觉错位;同时 body 宽度因滚动条消失而突变,又叠加横向“抽搐”。
为什么 focusin 触发时 getBoundingClientRect() 拿不到真实位置
focusin 是键盘弹出前的信号,此时 DOM 尚未重排、visualViewport 也未更新,input.getBoundingClientRect().bottom 返回的是全屏状态下的坐标。直接用这个值算 bottom 偏移,必然偏大或偏小。
- 必须加
setTimeout(() => { /* 读取 */ }, 0),把取值逻辑推到下一帧,等浏览器完成视口收缩和布局计算 - 别用
offsetTop或clientHeight,它们在 relative 祖先下不可靠,只认getBoundingClientRect() - 多个输入框共用同一 fixed 弹窗?得用全局变量缓存
document.activeElement,避免 blur 后恢复错位
为什么 padding-right: calc(100vw - 100%) 能防横向抖动,但对键盘跳动无效
这个技巧只解决滚动条消失导致的 body 宽度跳变,属于横向 layout shift;而键盘弹出引发的是纵向视口塌缩,100vw 在 iOS 上始终等于物理屏幕宽,不随键盘变化,所以它压不住纵向错位。
-
calc(100vw - 100%)对键盘问题完全无感,混用会掩盖真正原因 - 老版 Safari(iOS 14 及以下)连这个 calc 都不支持,得 JS 补位,但那只是横向补丁
- 纵向错位必须靠
focusin/blur+visualViewport或window.innerHeight差值判断
为什么监听 resize 不可靠,却总有人试
iOS Safari 的 resize 事件对软键盘响应率低于 20%,安卓各 WebView 行为分裂:Chrome 延迟 300ms+,微信 WebView 常根本不触发。它不是延迟问题,而是根本没被设计用来响应键盘。
立即学习“前端免费学习笔记(深入)”;
- resize 只适合横竖屏切换这类明确的视口变更,不能当键盘信号用
- 如果已绑了 resize,务必降级为兜底:仅在 focusin 未触发或
visualViewport.height未变时才启用 - 真机测试时,打开 DevTools 远程调试,直接监控
visualViewport.height变化比看 resize 更准
最易被忽略的点是:blur 后不恢复定位 + 不清除 inline 样式 = 滚动即错乱。哪怕 focusin 处理再完美,漏掉 el.style.removeProperty('bottom') 和 window.scrollTo(0, 0),fixed 元素就会在后续滚动中彻底脱离锚点。


















