应使用100dvh替代100vh解决iOS Safari中地址栏收放导致的视口高度错位问题,因其动态响应当前可视区域变化,而100vh仅锁定页面加载时的初始布局视口高度。

为什么100vh在iOS Safari里总比看到的高
因为浏览器把100vh锁死在页面加载瞬间的layout viewport高度上,而这个高度包含了地址栏、底部导航栏等UI控件。用户一滚动,地址栏收起,window.innerHeight变大了,但100vh的像素值根本没更新——结果就是轮播图被切顶、固定按钮被遮住、全屏容器底下留白。
Android Chrome 同样如此,软键盘弹出时window.innerHeight骤降,vh却纹丝不动。
这不是你写错了CSS,是vh单位本身的设计缺陷:它不响应视口可用区域的实时变化。
用document.documentElement.style.setProperty('--vh', ...)动态设1vh
核心思路是用JS把当前真实可视高度(window.innerHeight)按百分之一拆解成px值,存进CSS变量--vh,再在CSS里用calc()复原100%高度。
立即学习“前端免费学习笔记(深入)”;
关键点:
-
--vh必须是px单位,不能是1vh或空字符串;否则calc(var(--vh) * 100)会失效(浏览器不支持相对单位参与乘法) - 初始值要设:
:root { --vh: 1px; },避免JS未执行时calc(var(--vh, 1vh) * 100)回退到1vh导致元素塌缩 - 监听事件不能只靠
resize:iOS地址栏收起不触发resize,得加visualViewport?.addEventListener('resize', ...) - 软键盘场景下,
visualViewport在部分Android WebView中不可靠,建议同时监听scroll(节流后)作为补充
示例JS:
function setVh() {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
setVh();
window.addEventListener('resize', setVh);
if ('visualViewport' in window) {
visualViewport.addEventListener('resize', setVh);
}
100dvh、100svh、100lvh怎么选
这些是2024年后逐步落地的新单位,解决的就是vh的静态缺陷:
-
100dvh:动态视口高度,随地址栏/键盘显隐实时更新。iOS 16.4+、Chrome 100+ 原生支持。最接近“你看到多高就占多高”的语义,但旧版不支持,需@supports (height: 100dvh)包裹并提供fallback -
100svh:小视口高度,即工具栏完全展开时的最小可用高度。适合底部固定按钮——确保它永远不被地址栏顶上去 -
100lvh:大视口高度,即工具栏完全收起时的最大高度。适合启动页、广告全屏页,追求最大沉浸感
注意:svh/lvh不是动态值,而是两个锚点常量;它们不会跳变,但也不能替代dvh做通用“当前可见高度”。
绝对定位容器用100vh类单位为什么会失效
当元素是position: absolute,且其父容器没有显式高度(比如height: auto),那么它的100vh虽然能算出像素值,但布局行为可能已脱离预期——因为绝对定位元素的百分比高度依赖包含块高度,而包含块若无高度,100%就等于auto,此时vh单位虽生效,但上下文已错位。
解决方案很具体:
- 给最近的
position: relative祖先加height: 100% - 确保其父链上有明确高度来源,例如
html, body { height: 100%; } - 或者直接放弃百分比高度逻辑,改用
top: 0; bottom: 0;配合100dvh或JS计算的--vh
最容易被忽略的是:这个失效不报错,也不明显,只在某些机型+特定滚动状态下才暴露——比如iOS Safari地址栏收起后,绝对定位的弹层突然上移几十像素。


















