应使用 dvh 替代 vh 实现动态全屏布局,ios 16.4+ 和 chrome 100+ 原生支持;旧版需通过 scroll/resize/orientationchange 监听 + requestanimationframe 节流更新 css 自定义属性 --dvh 作为 fallback。

移动端 Safari 地址栏遮挡导致 100vh 不等于可视高度
在 iOS Safari(尤其是 iOS 15+)中,100vh 指的是整个设备视口高度(device-height),而不是当前滚动时用户实际看到的区域。地址栏收起/展开会动态改变可视区域,但 100vh 值不变,导致内容被遮挡或留白。
典型现象:固定底部按钮被地址栏盖住;全屏轮播图顶部被切掉;height: 100vh 的容器在页面滚动后出现错位。
- 不要依赖
100vh实现“真·全屏”布局,尤其在需要底部交互的场景 - iOS Safari 不支持
dvh(dynamic viewport height)直到 iOS 16.4,旧版本必须降级处理 - 安卓 Chrome 支持
dvh较早(Chrome 100+),但需注意 WebView 兼容性
用 dvh 替代 vh 是最简方案(iOS 16.4+ / Chrome 100+)
dvh 是 CSS 新单位,代表“动态视口高度”,会随地址栏显隐自动更新,比 vh 更贴近用户真实可见区域。
直接替换即可生效,无需 JS 干预:
body {
height: 100dvh; /* ✅ 动态适配 */
}
.fullscreen {
height: 100dvh; /* ✅ 滚动时不会错位 */
}
- 仅当目标环境明确支持时才用 —— 可通过
@supports (height: 100dvh)做条件覆盖 - 不支持时,
100dvh会被浏览器忽略,需 fallback 到100vh或 JS 计算值 -
dvh与vh数值差异在 iOS 上可达 100px+(地址栏高度),不能简单估算
兼容旧版 iOS 的 JS 回退:监听 resize + scroll 动态更新高度变量
对 iOS 15 及更早版本,必须用 JS 获取实时可视高度,并写入 CSS 自定义属性供样式使用。
关键点不是“获取一次”,而是“感知变化”:
- 仅监听
resize不够 —— iOS Safari 地址栏显隐不触发 resize,要加scroll和orientationchange - 避免高频更新:用
requestAnimationFrame节流,否则滚动卡顿 - 初始高度必须在 DOM ready 后立即设置,否则首屏渲染仍用错误
vh
示例核心逻辑:
function updateDvh() {
document.documentElement.style.setProperty('--dvh', `${window.innerHeight}px`);
}
updateDvh();
window.addEventListener('scroll', updateDvh, { passive: true });
window.addEventListener('resize', updateDvh);
// 注意:iOS 有时 scroll 不稳定,可加定时 fallback(如 300ms 重置)
CSS 中这样用:
.fullscreen {
height: 100dvh;
height: calc(var(--dvh, 100vh) * 1);
}
为什么不用 100% 或 100svh?
100% 依赖父元素高度,在 flex/grid 容器里常失效;100svh(small viewport height)是“最小可能视口高度”,固定为地址栏展开时的高度,无法响应收起动作 —— 它解决的是另一类问题(比如弹窗防滚动穿透),不是地址栏遮挡。
-
svh在 iOS Safari 中支持度差(iOS 16.4+ 才有),且数值偏小,会导致内容永远显示不全 -
lvh(large viewport height)同理,是地址栏收起时的高度,但无法应对用户手动拖出地址栏的场景 - 三者(
svh/lvh/dvh)行为差异极大,混用会放大布局错乱
真正可靠的只有 dvh(新环境)+ JS 动态 --dvh(旧环境),其他都是权宜之计。
最麻烦的不是写法,而是 iOS 地址栏行为本身没有标准事件通知 —— 它藏在滚动惯性、缩放、甚至键盘唤起的副作用里,所以 fallback 必须带容错和节流。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











