vw/vh是相对于视口宽高的单位(1vw=1% viewport width),与%不同,它不依赖父容器而始终基于窗口尺寸;直接替代百分比易导致溢出、截断或响应失配,应结合dvh、clamp()及min/max函数按需使用。

vw/vh 是什么,为什么不能直接当“百分比”用
它们是相对于视口宽度/高度的单位,1vw = 视口宽度的 1%,1vh = 视口高度的 1%。但和 % 的关键区别在于:后者相对父容器,前者永远只认 window.innerWidth 和 window.innerHeight —— 这意味着嵌套再深,50vw 也永远是屏幕一半宽,不是父元素一半宽。
常见错误现象:width: 100% 在 flex 容器里撑满父级,换成 width: 100vw 却溢出滚动条;或者在有固定 header 的页面里用 height: 100vh 做全屏区块,结果内容被截掉——因为 100vh 包含了地址栏、系统状态栏等不可见区域(尤其 ios safari)。
- 移动端 Safari 中,
vh在地址栏收起/展开时不会实时重算,导致布局错位 - 使用
min-height: 100vh比height: 100vh更安全,避免内容少时留白、内容多时截断 - 需要“视口减去 header 高度”时,别手写
calc(100vh - 64px),优先考虑100dvh(见下一条)
100dvh 替代 100vh 是当前最稳妥的全屏高度方案
dvh(dynamic viewport height)是 css 新增单位,代表“浏览器实际可渲染区域的高度”,会动态响应地址栏伸缩、键盘弹出等变化。chrome 105+、Safari 16.4+、firefox 114+ 已支持;不支持时可降级为 100vh。
使用场景:登录页全屏背景、模态框遮罩层、单页应用根容器高度控制。
立即学习“前端免费学习笔记(深入)”;
实操建议:
- 优先写
height: 100dvh,再加一行height: 100vh作为降级(CSS 层叠顺序保证新单位覆盖旧单位) - 不要依赖 JavaScript 监听
resize去手动更新vh值——开销大且无法捕获地址栏变化 - 注意:
dvh不是所有浏览器都支持,可以用@supports (height: 100dvh)包裹关键样式做渐进增强
vw/vh 做字体响应式时,line-height 和 padding 容易失配
用 font-size: clamp(1rem, 4vw, 2rem) 能让文字随屏幕缩放,但若同时设 line-height: 1.5(无单位),行高会按当前 font-size 计算,看起来还行;一旦设成 line-height: 2rem 或 padding: 1vh,就可能在小屏上挤成一团,或在大屏上空得离谱。
核心问题:不同属性对视口单位的敏感度不同,font-size 缩放影响整个行盒,而 padding 是独立作用于盒模型的。
- 推荐统一用
rem或无单位值控制line-height,保持比例稳定 -
padding和margin若必须响应式,优先用clamp()包裹,比如padding: clamp(0.5rem, 2vw, 1.5rem) - 避免混用
vw和px做 border 或 shadow,小屏下1px可能比0.1vw还粗,视觉断裂
vw/vh 在 flex/grid 容器中与 min/max-width/height 冲突很隐蔽
比如给一个 display: flex 的卡片容器设 width: 80vw,再加 max-width: 1200px,本意是“最大不超过 1200px”,但实际在 1920px 屏幕上,80vw = 1536px,此时 max-width 才生效。可如果写成 width: min(80vw, 1200px),逻辑才真正符合预期。
常见错误现象:卡片在桌面端突然变窄、grid item 在大屏上错位、图片容器宽高比崩塌。
- CSS
min()/max()/clamp()是解决这类冲突的首选,比媒体查询更简洁 - flex item 上同时设
flex-basis和width时,width用vw可能被 flex 算法忽略,优先用flex-basis: 80vw - grid template columns 里慎用
1fr和20vw混排,小屏下20vw可能小于最小内容宽度,触发隐式 overflow
视口单位本身没毛病,问题总出在“以为它和百分比一样听话”,其实它只听视口的,不听布局上下文。真正要用稳,得时刻问一句:这个值,是该跟着屏幕变,还是该跟着父容器变?