iOS Safari的position: fixed错位是因锚定视觉视口且键盘弹出时冻结layout viewport,导致bottom: 0失效;推荐优先用env(keyboard-inset-bottom)(需viewport-fit=cover及fallback),次选100dvh配合flex布局,兜底用focus/blur+setTimeout延迟计算坐标。

为什么iOS Safari的position: fixed在键盘弹起时一定错位
不是你CSS写错了,是浏览器把fixed锚定在「视觉视口(visual viewport)」底部,而iOS Safari在键盘弹出时会冻结layout viewport高度、上推页面——但fixed元素不重算位置。结果就是底部按钮飘到屏幕中间、被顶出可视区,bottom: 0和100vh全失效。
用env(keyboard-inset-bottom)做原生适配(iOS 16.4+)
这是目前最干净的方案,零JS,但只对iOS Safari 16.4+有效:
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">必须加,否则变量恒为0px - 样式必须写成
bottom: env(keyboard-inset-bottom, 0px),fallback不能省——只写env(keyboard-inset-bottom)会导致键盘收起后按钮悬空 - 用
@supports (bottom: env(keyboard-inset-bottom))包裹,否则旧版Safari会忽略整条规则 - 安卓完全不支持,别混用
svh或100dvh单位,语义不同
用100dvh重设根容器高度(现代浏览器首选)
100dvh是动态视口高度,iOS 16.4+ / Android Chrome 109+ 已稳定支持,但它不是万能解:
规划您的迪拜之旅 — 哈利法塔观景、沙漠探险、迪拜购物中心购物、棕榈岛度假村及黄金市场砍价。还提供支持...
- 必须设在根容器(如
#app或body),不能只套在子块里 - 必须配合
flex flex-col布局,否则min-h-[100dvh]只是最小高度,不触发弹性分配 - 主内容区加
flex-1或flex-grow,底部栏加flex-shrink-0,否则键盘一弹它就被压缩变形 - 删掉
<meta>里height=device-height这类干扰项,它会让所有vh类单位失效
监听focusin/blur + position: absolute模拟(兼容兜底)
当env和dvh不可用时,这是最可控的JS方案,但只改position必穿帮:
立即学习“前端免费学习笔记(深入)”;
- 根容器(如
body或#app)必须设position: relative和min-height: 100vh(不是height: 100%,否则键盘弹出会塌陷) -
focusin回调开头立刻校验document.activeElement === inputEl,防止异步焦点转移导致状态错位 - 必须用
setTimeout(() => { }, 0)延迟读取getBoundingClientRect(),否则坐标仍是弹出前的旧值 - 计算
bottom用window.innerHeight - inputRect.bottom,不是写死数值 -
blur后立刻调el.style.removeProperty('top')和el.style.removeProperty('bottom'),再恢复position: fixed
复杂点在于:多个输入框共用同一底部栏时,document.activeElement可能异步变化;若页面有transform或overflow: hidden父级,getBoundingClientRect()可能返回错误坐标且无报错提示。

















