这不是 CSS 写错了,而是 iOS Safari 将 fixed 锚定在视觉视口底部,键盘弹起时视口上推但定位不重算,导致按钮飘至屏幕中间或卡在键盘上方;需用 focusin/blur 监听(非 resize)、env(keyboard-inset-bottom) 配 viewport-fit=cover 降级、absolute 替代时确保祖先容器稳定且动态切换。

这不是 CSS 写错了,而是 position: fixed 在 iOS Safari 中被锚定在「视觉视口(visual viewport)」底部,而软键盘弹起时,这个视口会被上推或冻结——fixed 元素却不会重算位置,结果就是底部按钮飘到屏幕中间、卡在键盘上方,甚至被顶出可视区。
为什么 focusin/blur 是唯一可靠监听入口
iOS 上 resize 事件触发率极低(实测低于 20%),且常只在键盘收起时才触发一次,完全漏掉弹出瞬间;focus 不冒泡,容易丢失事件;focusin 冒泡稳定,能捕获任意可聚焦元素(input、textarea、[contenteditable])获得焦点的时刻。
- 必须立刻校验
document.activeElement === inputEl,防止异步焦点转移导致状态错位 - 监听范围不能只写
input,漏掉textarea就等于漏掉一半真实场景 -
blur回调里必须立刻恢复原始定位,否则滚动时元素直接消失
为什么 env(keyboard-inset-bottom) 不能单独用
bottom: env(keyboard-inset-bottom, 0px) 是 iOS Safari 16.4+ 原生支持的最优解,但它的生效有硬性前提:
- 必须加
<meta name="viewport" content="viewport-fit=cover">,否则不生效 - 必须用
@supports (bottom: env(keyboard-inset-bottom))包裹,否则旧版 Safari 会忽略整条规则 - 安卓完全不支持,别和
svh混用(比如calc(0px + 100svh)是错的) - fallback 值
0px不能省略,否则键盘收起后按钮悬空
为什么 absolute 替代 fixed 还是错位
只改 position: absolute 会同样穿帮,因为 absolute 的基准是最近的 position: relative 祖先容器,而这个容器本身可能塌陷:
立即学习“前端免费学习笔记(深入)”;
- 根容器(如
#app或body)必须设position: relative和min-height: 100vh(不能只写height: 100%) - 底部元素必须加
width: 100%,否则横屏或缩放后偏移 - 必须监听
focusin/blur动态切换定位状态,不能靠 CSS 静态声明 -
focusin后必须用setTimeout(() => {}, 0)延迟读取getBoundingClientRect(),否则坐标仍是弹出前的旧值
最容易被忽略的细节
所有计算前,必须确认 document.activeElement === inputEl;若页面用了 transform、overflow: hidden 或 fixed 父容器,getBoundingClientRect() 可能返回错误坐标,且无任何报错提示。


















