移动端 fixed 错位本质是浏览器未将键盘视为视口变化:ios 冻结 layout viewport 而上推页面,android 压缩 window.innerheight 却不重算 fixed;absolute 模拟 fixed 是最兼容方案,需 focusin/blur 监听(resize 不可靠),根容器设 relative+100vh,底部区域动态切换定位并同步滚动锚点,env(keyboard-inset-bottom) 仅作辅助降级。

移动端 position: fixed 在输入法弹出时上浮或错位,不是样式写错了,而是浏览器根本没把键盘当作视口变化来响应——iOS 冻结 layout viewport 却上推页面,Android 压缩 window.innerHeight 却不触发 fixed 重算。用 absolute 模拟 fixed 是目前兼容性最广、可控性最强的落地方案。
为什么必须用 focusin/blur 而不是 resize
监听 window.onresize 在 iOS 上大概率不触发,安卓行为分裂:Chrome 可能延迟 300ms 才发事件,而 UI 错乱早已发生。focusin 是唯一稳定、及时、可预测的入口点——用户点进 input 的瞬间,你就该开始准备定位切换。
-
resize只能当兜底,比如处理横竖屏切换,不能作为主逻辑 - 在
resize回调里直接改style.top会强制同步重排,滚动卡顿明显 - 真机测试时,微信 WebView 中
resize触发率低于 20%
absolute 模拟 fixed 的三个硬约束
核心不是“换个定位方式”,而是让容器高度可控、状态可同步。fixed 失效是因为它锚定在不可靠的视口上;absolute 锚定在 relative 祖先上,只要这个祖先不随键盘乱跳,就能稳住。
- 根容器(如
#app)必须设position: relative和height: 100vh,同时加overflow: hidden或touch-action: manipulation禁止 body 滚动 - 底部区域用
position: absolute; bottom: 0; width: 100%,内部用display: flex,不依赖文档流 - 监听
focusin到input或textarea时,检查window.innerHeight是否比初始值小 >200px;若是,给底部区加keyboard-active类,切为position: static并设margin-top: auto
blur 后必须手动恢复滚动锚点
失焦后不恢复,滚动时元素会彻底错位甚至“粘”在段落上。尤其 iOS 微信中,收起键盘后常卡在高位,window.scrollTo(0, 0) 不是可选,是必做。
- blur 回调里必须立刻恢复
position: fixed和原始bottom: 0 - 恢复前先调用
el.style.removeProperty('top')和el.style.removeProperty('bottom'),避免残留 inline 样式干扰 - 必须用
setTimeout(() => { window.scrollTo(0, 0) }, 100)强制恢复滚动锚点,iOS 对scrollTo有节流策略,太短无效
env(keyboard-inset-bottom) 不能替代 JS 切换逻辑
env(keyboard-inset-bottom) 仅 iOS 16.4+ 支持,安卓完全不认。它只能帮你动态上浮 fixed 元素,但无法解决 blur 后状态不同步、滚动错位、多输入框竞争等问题。
- 不要和
svh混用,例如bottom: calc(0px + 100svh)是错的——语义冲突,无实际意义 - 必须搭配
@supports (bottom: env(keyboard-inset-bottom))做降级,否则旧版 Safari 会忽略整条规则 - 即使用了该变量,仍需监听
focusin/blur来控制容器高度、同步状态、防止内容被顶穿
真正难的不是写几行 JS 切换定位,而是确保所有状态(视口高度、active 元素、滚动位置、CSS 类)在 focus/blur/resize/visualViewport 多个信号之间严格同步。漏掉任意一环,真机上就表现为“偶尔错位”“收起后卡住”“切输入框时闪一下”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











