
如果只盯着vw、vh、rem、em这几个老面孔你可能还没意识到——CSS 的单位家族过去几年悄悄翻了好几倍。从svh、dvh到cqw、cqh再到ch、ic、cap、lh一不留神能叫出名字的 CSS 单位已经几十个了。标题用“屎里淘金”不是夸张大部分新单位确实只在极少数场景有用普通业务项目写不到那么细。但反过来真正值得进项目的那几个解决的都是过去相当折磨人的问题。先说结论看着多真正需要写进业务代码的其实不超过十个。这篇文章先把单位体系完整捋一遍然后重点拆解五个最容易被坑的方向——移动端视口高度跳变、容器查询单位、逻辑属性单位、流式字号数学函数、以及那些偶尔能派上用场的冷门单位。每个方向都会给出可复制的 CSS 代码、判断标准以及遇到异常时怎么排查。适合读者做过移动端 H5、写过响应式页面、或者正在封装组件库的前端同学。如果你正在纠结100vh底部按钮掉出屏幕、卡片放进不同容器不会自适应、字号在手机和平板之间切换不够顺滑这篇文章可以重点看一下dvh和cq*两章。1. CSS 单位总览几十个单位到底从哪来CSS 单位并不是某个版本一次性加完的而是分成好几批持续进入规范。按功能和设计意图可以分成下面几类。类别单位列表典型作用绝对长度单位px、pt、pc、cm、mm、in固定尺寸测量字体相对单位em、rem、ch、ex、ic、cap、lh、rlh根据字号、字符宽高、行高计算百分比%相对包含块传统视口单位vw、vh、vmin、vmax相对视口宽高小/大/动态视口单位svw、svh、svi、svb、svmin、svmaxlvw、lvh、lvi、lvb、lvmin、lvmaxdvw、dvh、dvi、dvb、dvmin、dvmax针对移动端浏览器 UI 收展场景逻辑视口单位vi、vb按书写方向区分 inline / block容器查询单位cqw、cqh、cqi、cqb、cqmin、cqmax相对最近容器查询容器其他角度/时间/分辨率单位deg、rad、grad、turn、ms、s、dpi、dpcm、dppx用于动画、渐变、媒体资源等这里光视口单位就有 24 个传统 6 个 小、大、动态三组各 6 个加上容器查询 6 个、字体相关 8 个以及其他特定用途单位总数逼近 50 个。“一不留神蹦出几十个”并不是夸张。新单位集中在三个方向上。第一旧视口单位在移动端暴露问题于是规范补充了sv*、lv*、dv*三套视口尺寸单位。第二组件化开发兴起同一个组件可能出现在完全不同的容器宽度里媒体查询只看视口显然不够于是有了容器查询和cq*单位。第三国际化与逻辑属性推广把布局方向从物理方向抽象成逻辑方向于是有了vi、vb。后面几章逐一展开。2. 视口单位改造dvh/svh/lvh 解决 100vh 的坑移动端最经典的 CSS 问题之一在 iPhone 上Safari 地址栏收起时视口高度会变大地址栏展开时视口高度变小。100vh在不同浏览器里到底按哪种高度计算并不统一结果就是用height: 100vh写全屏弹层或底部吸底按钮页面滚动时高度忽大忽小按钮偶尔被地址栏挡住偶尔又悬在半空。2.1 svh、lvh、dvh 分别代表什么svh小视口高度。地址栏占满时真正能显示内容的那块区域高度。lvh大视口高度。地址栏收起后整个可视区域高度。dvh动态视口高度。它会跟随浏览器 UI 的展示状态实时变化。地址栏展开时接近svh收起后接近lvh。用公式表达就是dvh会随着浏览器界面变化在svh和lvh之间动态切换。也正因为这个特性移动端全屏容器用dvh通常最省心。2.2 代码实现最稳妥的写法是先用100vh打底再用100dvh覆盖.fullscreen-box { height: 100vh; height: 100dvh; }这段代码的逻辑老浏览器不认识dvh直接忽略第二行使用第一行的vh支持dvh的浏览器使用第二行获得更好的动态体验。这是经典的渐进增强写法也是新单位推广时最推荐的落地方式。2.3 什么时候该用哪个场景推荐单位原因移动端全屏弹层 / 全屏页面dvh跟随地址栏动态变化体验最自然移动端固定底部按钮 / 吸底栏dvh safe-area 配合避免按钮被地址栏和底部横条遮挡滚动长页面中的占位高度lvh一般以上滑后地址栏收起的大视口为准需要稳定“最小可见高度”的组件svh保证地址栏展开时也不会溢出吸底按钮场景里安全区也要考虑进来。常见做法是预留env(safe-area-inset-bottom).bottom-bar { padding-bottom: calc(env(safe-area-inset-bottom) 8px); }如果没用过env()可以把它理解成浏览器提供的一类环境变量专门用于适配刘海屏、底部圆角等安全区域。它不是单位但在移动端布局里和dvh经常一起出现。兼容性方面dvh、svh、lvh在 Chrome 108、Safari 15.4、Firefox 101 已经默认支持。iOS 15.4 之前的 Safari 不支持所以老项目保留vh回退是关键。2.4 老项目回退方案如果项目需要同时兼容旧浏览器可以在 JS 里做一次特性检测然后给根节点加一个降级 classif (!CSS.supports(height: 100dvh)) { document.documentElement.classList.add(no-dvh); }再在 CSS 里用 class 做兜底.no-dvh .modal { height: 100vh; } .modal { height: 100vh; height: 100dvh; }这样支持dvh的浏览器走动态高度不支持的浏览器退回vh不会因为兼容问题破坏布局。3. 容器查询单位cqw/cqh/cqi/cqb 让组件真正自适应媒体查询的问题是它只看视口宽度看不到组件父容器的宽度。同一个.card组件放在宽版主区域和窄版侧边栏即便视口是同一个视觉表现也大概率不同。媒体查询没法直接感知这种差异容器查询恰好解决这个问题。3.1 容器查询单位有哪些容器查询单位有 6 个cqw容器宽度的 1%。cqh容器高度的 1%。cqi容器内联方向尺寸的 1%。cqb容器块方向尺寸的 1%。cqmincqi和cqb中的较小值。cqmaxcqi和cqb中的较大值。在横向书写模式下cqi通常约等于容器宽度cqb通常约等于容器高度。cqmin和cqmax适合需要约束组件尺寸比例的场景例如头像、图标这类既要宽高相等又不能超过容器的元素。3.2 怎么用第一步给父容器设置container-type.card-list { container-type: inline-size; }container-type: inline-size表示只监听容器内联方向的尺寸变化使用限制更少是组件中最常见的配置。第二步在子元素里使用cq*单位或者配合container规则.card { padding: 2cqi; } container (min-width: 480px) { .card-title { font-size: 1.75rem; } }这样同一个.card放到窄容器中是小字号、紧凑内边距放到宽容器中字号和内边距自动放大不再依赖外层页面是手机还是桌面。头像类组件用cqmin可以保持比例防溢出.avatar { width: 30cqmin; height: 30cqmin; }这个写法保证头像的宽高都不会超过容器较短的边在卡片宽度和高度都变化时尤其省心。3.3 容器查询单位使用场景用到cq*的场景通常具备两个特征组件会被复用且出现的容器宽度差异较大。典型例子是电商商品卡片、后台列表的统计卡片、富文本编辑器里的嵌入块。需要特别注意cqw等单位的计算前提是存在“容器查询容器”。如果祖先链里没有任何带container-type的元素容器查询单位会退回视口单位来参与计算这容易导致意外效果。排查时优先检查最近的容器元素是否设置了container-type。4. 逻辑单位vi/vb 与多语言布局的关系vi和vb是逻辑视口单位它们不按“物理方向”计算尺寸而是按文档书写方向计算。vi内联inline方向的视口尺寸也就是文字书写的水平方向。vb块block方向的视口尺寸也就是块级元素堆叠的垂直方向。在常见的从左到右、从上到下的页面里vi约等于vwvb约等于vh。对于阿拉伯语这类从右往左书写的页面margin-inline-start、padding-inline-end这类逻辑属性会自动跟随方向vi也依然表示“对应书写方向上的水平尺寸”。这样写出来的样式天然适配多语言。一个简单例子.banner { width: 90vi; margin-inline: auto; }相比width: 90vw90vi在 LTR 与 RTL 页面中的行为差异更可控。如果你的项目暂时只做中文和英文vi/vb带来的收益不明显但一旦要考虑阿语、希伯来语或者日文竖排这类布局逻辑属性和逻辑单位能省下大量覆盖样式。再配合逻辑属性做长文本阅读优化.article { max-inline-size: 70ch; text-align: start; }这里70ch限制的是“大约 70 个0字符的宽度”对中文内容也能提供一个可读性不错的行宽阈值。text-align: start在 LTR 里是居左在 RTL 里自动居右不需要手动判断语言方向。5. 数学函数clamp/min/max 是新单位的最佳搭档严格说clamp()、min()、max()不是单位但它们是把新单位组合成实际效果的表达层。很多“看起来用了新单位”的页面真正发挥力量的其实是这几个数学函数。5.1 流式字号h1 { font-size: clamp(1.5rem, 4cqi 0.5rem, 2.5rem); }这个表达式的含义最小字号 1.5rem推荐字号为容器宽度的 4% 加上 0.5rem最大不超过 2.5rem。字号会随着容器宽度平滑变化中间不需要写任何媒体查询。这里的4cqi换成4vw也可以如果组件放在容器查询环境里cqi会让字号更贴合组件实际宽度。5.2 自适应间距和宽度.container { width: min(100%, 1200px); padding: clamp(12px, 2cqi, 24px); }min(100%, 1200px)是过去项目里常见的经典写法宽度优先取 1200px但当屏幕宽度不足时自动回退到 100%。clamp(12px, 2cqi, 24px)则让内边距跟随容器宽度连续伸缩。5.3 动态圆角.card { border-radius: min(3vw, 16px); }小屏时圆角不会太大大屏时圆角固定不超过 16px。这种写法比判断屏幕档位写多个媒体查询轻量得多。5.4 运算时的空格问题CSS 计算表达式的运算符两侧必须留空格/* 正确 */ font-size: clamp(1rem, 2vw 0.5rem, 1.5rem); /* 错误可能被解析成无效值 */ font-size: clamp(1rem, 2vw0.5rem, 1.5rem);这个细节极易踩坑排查clamp()不生效时优先检查空格和整个表达式是否与属性数据类型匹配。6. 冷门单位盘点ch/ic/cap/lh/rlh 各自的价值6.1 ch 与 icch等于当前字体数字0的宽度ic等于当前字体下一个全角 CJK 字符的宽度。对中文产品来说ic比ch更适合限制中文文本行宽.article { max-width: 60ic; }这段代码表示正文行宽约为 60 个汉字宽度比写死像素值更接近中文阅读体验。ch则常用于等宽代码块宽度控制.code-block { max-width: 80ch; }等宽字体下80ch约等于一行容纳 80 个字符和 GitHub 代码阅读习惯比较接近。6.2 cap、lh、rlhcap是字体大写字母高度lh是当前元素行高rlh是根元素行高。这类单位本身定义很规范但日常业务里的适用场景非常窄。多数情况下可以直接用倍数表达实现同样效果比如行高用line-height: 1.6不必借助lh单位。6.3 冷门单位的结论冷门不等于没用重点是不要为了“用新单位”而用。cap、lh、rlh至少知道存在遇到特殊排版需求能想起来即可不必进团队规范。真正在业务里频繁用到且能提升维护效率的通常在dvh、cq*、clamp()这三个范围内。7. 综合实操一个商品卡片适配窄侧栏和宽主区为了把上面的单位串起来做一个实际例子一个商品卡片放在窄侧栏和宽主区里都能自适应。先写 HTML 结构div classgrid aside classsidebar article classcard h2侧栏卡片/h2 p这里是较窄容器里的展示效果。/p /article /aside main classmain article classcard h2主区卡片/h2 p这里是较宽容器里的展示效果。/p /article /main /div再写 CSS.grid { display: grid; grid-template-columns: 260px 1fr; gap: 16px; } .sidebar, .main { container-type: inline-size; } .card { padding: clamp(12px, 2cqi, 24px); border: 1px solid #ddd; border-radius: min(3vw, 14px); } .card h2 { font-size: clamp(1rem, 3cqi, 1.5rem); } container (min-width: 400px) { .card h2 { font-size: 1.5rem; } }运行后观察侧栏里的卡片cqi依据侧栏宽度计算字号被限制在较小范围主区里的卡片依据主区宽度计算字号可以更大。换到手机端把grid-template-columns改成单列卡片依然能自动适配。这个例子同时用了容器查询单位、clamp()、min()三种能力没有写一条媒体查询。7.1 验证方式打开浏览器 DevTools选中.card元素在 Computed 面板里查看padding、font-size的最终计算值。再用设备模拟器切换不同视口宽度确认容器变宽时字号连续变化。判断标准很简单侧栏卡片和主区卡片应该出现明显但不过度的尺寸差异并且两端极限宽度下内容不溢出、不错位。8. 兼容性与降级策略新单位再好也不能忽视老浏览器。部署前先查兼容性推荐两个途径一是 Can I Use 网站直接搜dvh、container queries等关键词二是用代码做特性检测。8.1 supports 检测 CSS 特性supports (height: 100dvh) { .modal { height: 100dvh; } }8.2 JS 检测if (CSS.supports(height: 100dvh)) { console.log(支持 dvh); } if (CSS.supports(container-type: inline-size)) { console.log(支持容器查询); }JS 检测的好处是能配合状态开关切换降级样式。比如容器查询不支持的浏览器可以给组件额外加一套基于vw的近似尺寸兜底。8.3 推荐降级组合新能力推荐回退方案dvh / svh / lvh先用 vh再用新单位覆盖cqw / cqh 等用百分比 媒体查询兜底clamp()先写一个固定 font-size再写 clamp容器查询 container用 min-width 媒体查询兜底降级组合的核心思路是“老浏览器读老值新浏览器读新值”同一段代码里用先后顺序实现覆盖比在 JS 里做大量分支更干净。9. 常见问题与排查方法问题现象可能原因排查方式解决方案100dvh没生效浏览器版本较老用CSS.supports检测保留vh回退或升级浏览器container条件始终不触发容器没有设置container-type检查祖先元素给容器加container-type: inline-sizecqw计算值一直为 0容器尺寸未被正确计算DevTools 查看容器元素确认容器可查询、宽高不是 0clamp()语法报错表达式缺少空格或运算符错误看控制台报错使用clamp(min, val, max)且运算符两侧加空格Safari 下dvh高度跳动Safari 版本较老或地址栏联动真机测试用svh或vh做兜底RTL 页面vi效果异常书写模式未设置检查dir与writing-mode设置对应dir属性后验证容器查询单位在组件库里失效父容器被overflow等属性影响逐个检查容器样式调整容器样式或换用视口单位排查时建议遵循一个顺序先确认浏览器是否支持该特性再检查计算链路里的祖先元素最后看计算属性面板里的实际像素值。大多数“单位没生效”的问题都能在这三步里定位。10. 最佳实践与使用建议移动端页面里优先把100vh替换成100dvh并保留vh作为回退解决吸底栏和全屏弹层的高度问题。改动成本很低收益却很直观。组件如果会被复用到不同宽度的容器里优先考虑容器查询单位和cq*而不是在父组件里写一堆视口媒体查询。容器查询的维护边界更清晰组件自己管自己的布局上层页面不需要操心它的内部表现。字号、间距、圆角这类连续变化的值用clamp()、min()、max()结合cqi或vw表达能明显减少媒体查询数量。但注意不要滥用固定像素值在某些地方仍然是正确选择比如 1px 边框、细分割线。设计系统或组件库更新时把单位统一封装到设计变量里业务代码不直接裸写dvh或cqw方便后续统一替换和降级。变量名的语义要清楚例如--space-card-padding、--font-size-hero不要只写--pd-1。团队内部可以约定一个“单位使用规范”哪些单位允许使用、哪些场景必须写回退、哪些单位碰到再查。规范不要写太长几十个单位里真正频繁出现的通常不超过五个。11. 总结回到标题那句话CSS 确实一口气蹦出了几十个单位把每一个都背下来没有必要。但有四个方向值得在项目里重点消化。第一dvh以及svh、lvh解决移动端视口高度跳变全屏与吸底场景直接受益。第二cq*这组容器查询单位解决组件级自适应是组件库和富系统页面的长效方案。第三clamp()、min()、max()本身不是单位但它们是让新单位落地为实用效果的关键表达式。第四vi/vb代表逻辑方向趋势做国际化布局时值得了解。如果时间有限推荐按这个顺序实验先在自己的移动端 H5 页面里替换一处100vh再给一个高频复用的卡片组件加上container-type和cqw最后用 DevTools 观察字号和内边距的连续变化。跑通这三步你对 CSS 新单位体系就有了一个实用层面的完整认识。剩下的单位等用到时再查也不迟。