Safari 的 100vh 会随地址栏收起/展开跳变,因其以动态视口高度为基准;应使用 @supports 检测 env(height) 设置 CSS 变量 --vh 作为稳定替代方案,兼容性不足时再用 JS 兜底。

为什么 Safari 的 100vh 在地址栏收起/展开时会跳变
Safari(尤其是 iOS 和 macOS 14–15)在滚动时会动态隐藏或显示地址栏,导致 100vh 对应的视口高度实时变化——不是固定为设备物理高度,而是随 UI 栏状态浮动。这会让全屏布局突然收缩或拉伸,比如轮播图裁剪、底部按钮上浮、position: fixed 元素错位。
根本原因:Safari 把地址栏区域算进 vh 单位的基准,而 Chrome/Firefox 始终以屏幕物理高度为基准。
直接用 100vh 做全屏容器,在 Safari 中属于「看似合理、实则不可靠」的写法。
用 env(height) + 100vh 回退方案获取稳定视口高度
iOS 11+ / macOS Monterey+ 支持 env(height)(也叫 env(safe-area-height)),它返回的是当前可用视口高度(不含地址栏),且不会随滚动抖动。但需注意它只在 Safari 有效,其他浏览器不识别,必须搭配回退逻辑。
立即学习“前端免费学习笔记(深入)”;
推荐写法:
:root {
--vh: 100vh;
}
@supports (height: env(height)) {
:root {
--vh: env(height);
}
}
之后所有需要全高容器的地方统一用 height: var(--vh),例如:
.full-screen {
height: var(--vh);
min-height: var(--vh);
}
-
@supports是关键,避免非 Safari 浏览器把env(height)当无效值导致整个声明失效 - 不要写成
--vh: 100vh, env(height)—— CSS 变量不支持逗号分隔的 fallback -
env(height)在 Safari 中始终反映“当前稳定可用高度”,比 JS 动态监听resize更可靠、无延迟
为什么不用 window.innerHeight + JS 动态设 CSS 变量
有人用 JS 获取 window.innerHeight 并写入 document.documentElement.style.setProperty('--vh', ...),看似灵活,实际在 Safari 中有明显缺陷:
- 首次加载时,JS 执行晚于 CSS 渲染,可能闪现错误高度
- 滚动触发地址栏收起后,
resize事件延迟约 200–400ms,期间布局已错乱 - iOS 16+ 引入了更激进的地址栏动画,JS 监听的时机更难捕捉
- 增加 JS 执行负担,对 LCP 等核心指标有负面影响
除非你必须兼容 iOS 10 或老版 macOS Safari(不支持 env()),否则纯 CSS 方案更干净、更及时。
额外兼容:iOS 10–11.3 需要 JS 补救
如果项目仍需支持 iOS 10 或 Safari 11.3 及更早版本(env() 尚未引入),可加一段轻量 JS 作为兜底:
if ('CSS' in window && CSS.supports('height', 'env(height)') === false) {
const setVh = () => {
document.documentElement.style.setProperty('--vh', `${window.innerHeight}px`);
};
setVh();
window.addEventListener('resize', setVh);
}
这段代码只在不支持 env() 的浏览器中运行,不影响现代 Safari。注意两点:
- 别用
orientationchange—— 它在 iOS Safari 中不可靠,且不触发地址栏变化 - 别在
DOMContentLoaded后立刻设,要等load或至少setTimeout(setVh, 0),确保渲染上下文就绪
真正麻烦的从来不是技术方案本身,而是 Safari 每次更新都悄悄改掉视口计算逻辑的节奏——所以优先用 env(height),再用 JS 填坑,而不是反过来押注 JS 的时机判断。


















