最安全做法是给 fixed 元素设 padding-bottom: env(safe-area-inset-bottom);必须配合 viewport-fit=cover meta,且 fallback 值不可省,否则在 iOS Safari 等环境会失效或遮挡底部操作区。

bottom: env(safe-area-inset-bottom) 是必须写的
直接写 bottom: 0 会让导航栏沉到物理屏幕最底,iPhone X 及后续机型的底部手势条(约 34px)会完全盖住按钮,点不动、看不见。这不是 bug,是 fixed 定位只认 viewport 边界、不感知安全区的必然结果。
正确写法是让元素“停在安全区起点”,即用 env(safe-area-inset-bottom) 抬高底边:
.nav-bar {
position: fixed;
bottom: env(safe-area-inset-bottom);
left: 0;
right: 0;
height: 60px;
}
- 必须配合
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">,否则env()返回 0 - 安卓和旧 iOS 会忽略该值,自动 fallback 为 0,无副作用
- 别写成
inset: auto auto env(safe-area-inset-bottom) 0—— 看似语义清晰,但部分微信 WebView 对inset简写支持不稳定,优先用明确的bottom
主容器 padding-bottom 要算两笔账
fixed 元素脱离文档流,主内容会从视口最底端开始渲染,真实“长”进导航栏下面。这不是视觉错觉,所以必须手动预留空间。
不能只写死 padding-bottom: 60px,得叠加安全区偏移:
立即学习“前端免费学习笔记(深入)”;
:root {
--nav-height: 60px;
}
main {
padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom));
}
- 用
padding-bottom,不是margin-bottom—— 后者在 flex/absolute 场景下易折叠或失效 - 值必须匹配导航栏真实高度:如果用了
box-sizing: content-box且含padding: 12px,实际高是 84px,硬写 60px 仍会遮挡 - 绝对不要在
body上设padding-bottom—— 会影响scrollHeight和IntersectionObserver判断
z-index 不是越高越好,尤其要防隐式层叠上下文
设 z-index: 9999 很可能压住系统级弹窗(如 iOS 分享面板、日期选择器),或被父容器截断。
- 建议留出缓冲区间:
z-index: 100给导航栏,z-index: 1000给弹窗,中间不空大档 - 检查导航栏外层是否用了
transform、filter或will-change—— 这些会隐式创建新层叠上下文,导致子元素z-index失效 - 真机调试时,在 Safari 开发者工具里看 computed 样式,确认
position仍是fixed,没退化成static
软键盘弹出时 fixed 导航栏会错位
iOS Safari 对 position: fixed 的重排支持不完整:键盘弹起/收起时,视口尺寸变了,但 fixed 元素坐标常卡在旧位置,导致“上浮”或“被顶起”。
- 绝对不要在导航栏内部放
<input>或<textarea>—— 这会强制重排视口,加剧错位 - 对所有可聚焦元素加
scrollIntoView({ behavior: 'smooth', block: 'nearest' }),确保聚焦时不被导航遮挡 - 微信 WebView 中可 UA 判断:
navigator.userAgent.includes('MicroMessenger'),必要时加body { position: relative; }防止滚动错乱


















