CSS 视口单位进化论:从 100vh 的「骗局」到 dvh 的救赎
关键词:CSS / 视口单位 / 移动端适配 / 响应式设计 / dvh / svh / lvh / 100vh bug / 前端工程化 / 用户体验
引言:一个持续了十年的移动端「谎言」
如果你做过移动端 H5 开发,或者写过全屏落地的营销页,你一定遇到过那个著名的「100vh 骗局」。
设计师给了你一张完美的设计稿:首屏全屏,底部按钮固定,不允许有滚动条。
你自信满满地写下:
.hero-section {
height: 100vh;
}
然后你在 iPhone 的 Safari 里打开,灾难发生了:底部的按钮被浏览器底部的工具栏遮住了一半,或者页面莫名其妙出现了一截滚动条。
(图解:左侧是理想设计,按钮在最底部;右侧是 Safari 现状,浏览器工具栏无情地盖住了你的按钮)
为了解决这个问题,前端社区发明了无数「歪门邪道」:
- 用
window.innerHeight动态计算高度赋给 CSS 变量--vh。 - 监听
resize事件疯狂重绘。 - 使用
position: fixed; top: 0; bottom: 0;这种古老的 hack。
为什么一个简单的「全屏」需求,在移动端会如此艰难?
这背后其实是 Web 标准与移动端原生 UI(动态地址栏、底部工具栏)长达十年的博弈。
好消息是,CSS 终于迎来了真正解决问题的新一代视口单位:svh、lvh、dvh 以及它们在宽度和逻辑方向上的兄弟们。
这不是简单的语法升级,这是 Web 终于承认:移动端的屏幕,从来就不是一个静态的矩形。
一、旧时代的妥协:为什么 100vh 不是真的 100% 高度?
1.1 完美的初衷与残酷的现实
在 PC 时代,vh (Viewport Height) 的定义非常完美:视口高度的 1%。浏览器窗口多高,100vh 就多高。
但在移动端,情况变了。iOS Safari 和 Chrome Android 为了给用户更多阅读空间,设计了动态工具栏:
- 初始状态:地址栏、底部工具栏展开,可视区域变小。
- 上滑状态:地址栏缩小/隐藏,工具栏消失,可视区域变大。
这时候,100vh 该等于「展开时的高度」还是「隐藏时的高度」?
1.2 浏览器的「摆烂」选择
早期的 Safari 做了一个影响深远的决定:为了避免滚动时页面元素跳动(Reflow),100vh 永远等于「地址栏隐藏时的最大高度」。
(图解:无论你的工具栏是否展开,Safari 始终认为 100vh 是那个「最大值」。这就导致当工具栏展开(实际只有 80% 高度)时,你的 100vh 内容溢出了 20%。)
这就解释了为什么你的按钮被遮住了:
- 浏览器告诉你 100vh 是 800px(假设全屏高度)。
- 但实际上底部的工具栏占了 80px,可视区域只有 720px。
- 你的按钮画在了第 750px 的位置,正好被工具栏盖得严严实实。
这个「特性」被无数开发者吐槽为 Bug,但浏览器厂商坚持这是「Feature」。
「他们为了滚动的丝滑,牺牲了布局的精确。
在很长一段时间里,我们只能用 JS 来修补 CSS 的谎言。」
二、新秩序的建立:svh、lvh、dvh 的三态哲学
W3C 终于意识到,移动端的视口不是一个固定的值,而是一个在「大」与「小」之间薛定谔的波动状态。
于是,CSS Viewport Units Level 3 引入了一套全新的单位体系,把视口高度拆分为三种状态。
2.1 svh (Small Viewport Height):永远不再被遮挡
定义:视口高度的最小值(即地址栏、工具栏全部展开时的可视高度)。
- 应用场景:首屏全屏页、底部固定按钮、Modal 弹窗。
-
价值:如果你写
height: 100svh,你的页面底部绝对不会被工具栏遮挡。它是最安全的「一屏」。
/* 以前的 JS Hack */
.modal {
height: calc(var(--vh, 1vh) * 100);
}
/* 现在的原生写法 */
.modal {
height: 100svh;
}
2.2 lvh (Large Viewport Height):沉浸式的极致
定义:视口高度的最大值(即地址栏、工具栏全部收起时的可视高度)。
- 应用场景:背景图、视差滚动容器。
-
价值:当你希望背景铺满整个「潜在」屏幕,不在乎一部分被遮挡时,用
lvh。它等同于旧时代的100vh。
2.3 dvh (Dynamic Viewport Height):动态的完美与代价
定义:动态视口高度。它会随着地址栏的伸缩,实时在 svh 和 lvh 之间变化。
- 应用场景:追求极致体验的流式布局。
-
代价:当用户滚动页面时,
100dvh的值会不断变化。这意味着浏览器可能需要不断重绘(Repaint)甚至重排(Reflow)。 -
现状:现代浏览器对
dvh做了很多优化,性能损耗在大多数场景下已可忽略不计。
三态哲学总结:
- svh 是保底的安全(Safe)。
- lvh 是理想的宏大(Large)。
- dvh 是真实的动态(Dynamic)。
三、维度的扩张:宽度、逻辑方向与极值
这套逻辑不仅修复了高度问题,还顺带重构了整个视口单位体系。
3.1 宽度的进化:svw / lvw / dvw
虽然移动端宽度的变化(主要是滚动条出现/消失)不如高度那么剧烈,但逻辑是一致的。
- vw:依然是默认视口宽度(通常包含滚动条)。
- svw / lvw / dvw:处理滚动条存在与否时的细微差异(在桌面端更明显)。
3.2 逻辑方向:vi / vb —— 国际化的必修课
随着 CSS 逻辑属性(Logical Properties)的普及,我们不再总是谈论 width 和 height,而是谈论 inline(文本流方向)和 block(块堆叠方向)。
-
vi (Viewport Inline):视口在行内方向的大小。
- 横屏/竖屏书写模式下:等于
vw。 - 竖排书写模式(如古诗词页面):等于
vh。 - svi / lvi / dvi:对应的三态变体。
- 横屏/竖屏书写模式下:等于
-
vb (Viewport Block):视口在块级方向的大小。
- 横屏/竖屏书写模式下:等于
vh。 - svb / lvb / dvb:对应的三态变体。
- 横屏/竖屏书写模式下:等于
实战案例:一个适配横竖屏书写的古诗卡片
.poem-card {
/* 无论横排还是竖排,永远占满视口在「块」方向上的 80% */
block-size: 80vb;
/* 无论横排还是竖排,永远占满视口在「行」方向上的 90% */
inline-size: 90vi;
/* 自动适配书写模式 */
writing-mode: vertical-rl; /* 切换这个属性,布局依然完美 */
}
3.3 极值单位:vmin / vmax 的三态
老朋友 vmin(vw/vh 中较小者)和 vmax(较大者)也全员升级:
- svmin / svmax
- lvmin / lvmax
- dvmin / dvmax
场景:做横竖屏适配的 H5 游戏或画报时,svmin 是保证内容在任何旋转状态下都完整可见的神器。
四、工程实战:如何在 2025 年写好一个全屏页面?
4.1 放弃 100vh,拥抱 dvh(但要做降级)
在 2025 年,如果你的目标浏览器支持率允许(iOS 15.4+, Chrome 108+),dvh 是全屏容器的最佳选择。
.full-screen-hero {
/* 降级方案:给旧浏览器一个固定的值 */
height: 100vh;
/* 现代方案:使用动态高度,完美贴合 */
height: 100dvh;
}
4.2 关键交互区域用 svh
对于底部的操作栏(Action Bar),不要用 dvh,因为你不想让按钮在用户手指滑动时「跳来跳去」。用 svh 锁定位置。
.bottom-bar {
position: fixed;
bottom: 0;
width: 100%;
/* 确保它永远在可视区底部,哪怕地址栏展开 */
bottom: calc(100vh - 100svh); /* 高级技巧:计算工具栏高度偏移 */
}
/* 或者更简单的布局思维: */
.app-container {
min-height: 100svh;
display: grid;
grid-template-rows: 1fr auto;
}
4.3 慎用 lvh,除非你在做特效
lvh 在实际 UI 布局中用得很少。它更多用于视觉背景,或者那种「滑一下就全屏」的沉浸式阅读体验。如果你用 lvh 做布局容器,用户大概率会因为点不到底部的按钮而骂娘。
「成熟的工程师懂得:
UI 的稳定性(svh)优先于 UI 的充满感(lvh),
而 dvh 是两者之间的优雅平衡。」
五、总结:从「对抗」到「接纳」
回顾 CSS 视口单位的进化史,其实是一部 Web 与移动端原生特性的磨合史。
- vh 时代:Web 试图假装自己还在 PC 上,无视了移动端复杂的 UI 变化,结果撞得头破血流(100vh bug)。
- JS Hack 时代:开发者用脚本强行修正高度,对抗浏览器的默认行为,性能差且代码丑陋。
- svh/dvh 时代:标准终于接纳了移动端的复杂性。我们不再强求一个「唯一的 100%」,而是承认「屏幕是会变的」。
这给了我们两个重要的启示:
- 不存在完美的静态适配。移动端设备极其碎片化,折叠屏、刘海屏、动态栏……拥抱动态(Dynamic)才是终极解法。
-
工具的粒度决定了体验的细腻度。从粗糙的
vh到精细的svh/lvh/dvh,前端工程化的本质,就是不断提升对像素级体验的掌控力。
下一次,当设计师问你「能不能把这个页面铺满全屏,不要滚动条,也不要被遮挡」时,
你可以自信地合上电脑(或者打开 VS Code),告诉他:
「当然可以,因为现在的 CSS,终于懂手机了。」