iOS Safari中vh跳变是因地址栏收起/展开改变window.innerHeight,导致vh值突变;应优先用calc(env(height)-env(top)-env(bottom))并fallback至100vh,或JS监听resize动态设置--vh变量,dvh暂不可单独依赖。

viewport 单位在 iOS Safari 滚动时跳变的根本原因
视口单位(vh、vw)在 iOS Safari 中滚动时“跳变”,不是 bug,而是浏览器真实行为:地址栏收起/展开会改变 window.innerHeight,而 vh 始终按当前可视区域高度的 1% 计算——滚动瞬间,vh 值突变,导致基于它的布局(比如全屏高度容器)视觉上抽搐。
用 calc() + env(height) 替代硬编码 100vh
iOS 15+ 和 Android Chrome 支持 env(height)(也叫 env(safe-area-height) 的变体),它反映的是「稳定视口高度」,不受地址栏缩放影响。但注意:env(height) 是实验性特性,需 fallback。
- 优先写
height: calc(env(height) - env(top) - env(bottom)),其中env(top)和env(bottom)是安全区偏移,能更精确适配刘海/Home Indicator - 必须加 fallback:
height: 100vh; height: calc(env(height) - env(top) - env(bottom));—— CSS 会忽略不支持的值,保留前一个 - 不要只依赖
env(height):它在某些 Android WebView 或旧版 Safari 中返回auto,直接导致高度塌陷
JavaScript 动态监听并修正 vh 值(兼容 iOS 14 及以下)
当 env() 不可用时,唯一可靠方式是用 JS 实时读取 window.innerHeight 并注入 CSS 变量。关键不是“监听 scroll”,而是监听 resize——iOS Safari 地址栏变化会触发 resize 事件。
- 在
DOMContentLoaded后立即执行一次:document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`) - 监听
resize,每次更新--vh:window.addEventListener('resize', () => { /* 同上 */ }) - CSS 中用
height: calc(var(--vh, 1vh) * 100),避免未定义变量导致计算失败 - 注意防抖:iOS 连续 resize 可能高频触发,加
setTimeout节流(300ms 内只取最后一次)
为什么不用 100dvh?它现在够稳吗?
100dvh(dynamic viewport height)是 CSS 新标准,理论上专为解决此问题设计,但它在实际中仍不可直接信任。
立即学习“前端免费学习笔记(深入)”;
- iOS Safari 16.4+ 才开始支持
dvh,且早期版本有渲染延迟:首次滚动后才生效,导致首屏闪动 - Android Chrome 109+ 支持,但部分国产 WebView(如微信内置)尚未透出该特性
- 不能替代 JS 方案:如果页面需要「滚动即响应」(比如固定底部按钮紧贴视口底边),
dvh的更新时机可能滞后于用户操作 - 建议仅作为渐进增强:
height: 100vh; height: 100dvh;,不单独依赖
最麻烦的地方从来不是写哪行代码,而是得同时照顾「iOS Safari 地址栏缩放时机」、「WebView 特性透出差异」和「CSS 计算链路中的 fallback 容错」——少一个环节,就可能在某个机型上白屏或错位。










