CSS新单位实战:dvh、cqi如何解决移动端视口与组件响应式问题 📅 发布时间:2026/9/3 18:13:15 👁 浏览次数: 一不留神CSS 新单位多了好几十个svh、dvh、lvh、cqw、cqi、cqh、lh、rlh、cap、ic……光看名字就够劝退一半人。但这些单位不是单纯凑数。真正的问题起点是移动端那个让人头疼的100vh。我曾经为了一个全屏引导页在真机上反复调高度最后发现问题不是布局而是“视口”在移动浏览器里本来就是动态的。地址栏一收工具栏一弹100vh 对应的高度就变了。我对这些新单位的态度很明确几十个里值得认真用起来的就两组——动态视口单位和容器查询单位它们会改变你对响应式布局的理解方式。另外几个字体相关单位属于特定场景的补充能用但不必全记。这篇文章我会把“为什么会出现这些单位”“每个单位适用在哪”“落地时怎么写回退”讲清楚最后给你一个排查流程免得新单位写进项目后半天查不出问题。1. 别再拿100vh当一屏动态视口单位才是移动端答案1.1 地址栏一收一缩100vh 就变了在桌面浏览器里100vh几乎等于“浏览器可视高度”用得很顺手。到了手机端事情变复杂了。Safari、Chrome 等浏览器的地址栏、底部工具栏会随着滚动收起或展开导致“视口”的实际高度一直在变化。很长一段时间里移动端浏览器为了降低页面抖动会把100vh解析成地址栏收起之后的最大高度而不是用户当前真正能看到的高度。这就带来两个经典问题全屏弹层底部被地址栏挡住滚动过程中背景高度突然跳了一下。过去最常见的粗暴方案是用window.innerHeight配合 JS 计算把高度写成内联样式。这能解决一部分问题但每次视口变化都要监听、重算、赋值又慢又啰嗦。现在 CSS 终于给出了原生解法把“视口”拆成小视口、大视口、动态视口三套单位。1.2 svh、lvh、dvh三组单位分别代表什么先记一个关系后面才不会混小视口small viewport浏览器工具栏完全展开、可用区域最小的时候。大视口large viewport浏览器工具栏完全收起、可用区域最大的时候。动态视口dynamic viewport当前真实可见的视口会随工具栏状态实时变化。对应到单位就是单位含义典型使用场景svh小视口高度的 1%需要保证内容不被工具栏遮挡比如底部按钮、弹层lvh大视口高度的 1%需要稳定最大高度时但实际用得不多dvh动态视口高度的 1%希望高度跟随当前真实可视区域比如全屏页面、滚动容器同样的规则也适用于svw、lvw、dvw只是单位从高度换成了宽度。宽度方向的动态变化在手机上不明显所以最值得先替换的是高度。1.3 移动端全屏布局的落地写法先回退再增强新单位虽好但你不能假设所有用户浏览器都认识。更稳妥的写法是先写老单位再写新单位让支持的浏览器用新值覆盖旧值。.hero { height: 100vh; /* 兜底不支持新单位时用 */ height: 100svh; /* 小视口兜底 */ height: 100dvh; /* 现代浏览器优先使用 */ }CSS 的层叠规则在这里很重要后面的声明会覆盖前面的声明。旧浏览器不认识svh、dvh会直接忽略继续使用100vh新浏览器则按顺序解析最后采用100dvh。如果你希望逻辑更明确也可以配合supportssupports (height: 100dvh) { .hero { height: 100dvh; } }注意声明顺序不能颠倒了。如果先把100dvh写在最前面再把100vh写在后面现代浏览器最后会采用100vh新单位等于白写。实际项目里我不建议把所有100vh都无脑替换成100dvh。有些场景要分清楚底部操作栏为了避免被工具栏遮挡更适合100svh全屏轮播图需要跟随当前视口用100dvh如果只是普通页面背景旧100vh也未必不能忍。按场景选而不是按“新旧”选。1.4 vi、vb 和 svmin/lvmax 这些单位暂时不用记视口单位里还有两个逻辑方向单位vi和vb。vi是视口内联方向尺寸的 1%vb是视口块方向尺寸的 1%。它们和 CSS 逻辑属性是一套思路会随着writing-mode变化。对于大多数中文、英文横向排版的页面vi近似宽度vb近似高度但暂时不是必需品。至于svmin、lvmin、dvmax这一组我认为属于“看一眼知道有这回事就行”的范畴。它们的语义太细日常布局很难比min()、max()函数更直观。真遇到需求再查规范也不迟。2. 容器查询单位让组件根据自己的父容器做响应式2.1 媒体查询只管页面管不到组件媒体查询media只能感知“视口”这个全局环境。页面宽度 1200px 时没问题但同一个组件被放到侧边栏时宽度只有 300px放到主内容区时宽度有 800px媒体查询就束手无策了。容器查询container解决了“组件根据自己父容器宽度自适应”的问题。容器查询单位则把这个能力进一步量化让元素不再只能靠视口单位或百分比而是可以直接用“容器宽度的百分之几”这种单位来设置字号、间距、尺寸。2.2 六个 cq 单位cqw/cqh/cqi/cqb/cqmin/cqmax容器查询单位一共有六个都相对于最近的“查询容器”单位含义cqw查询容器宽度的 1%cqh查询容器高度的 1%cqi查询容器内联方向尺寸的 1%横向排布下近似宽度cqb查询容器块方向尺寸的 1%横向排布下近似高度cqmincqi和cqb中较小的那个cqmaxcqi和cqb中较大的那个这里最需要记住的是cqi。因为实际场景中组件宽度响应的需求远大于高度。横向书写模式下cqi就相当于容器的宽度百分比。容器查询单位真正厉害的地方在于它不是相对页面也不是相对父元素百分比而是相对“离自己最近的查询容器”。这让组件可以脱离页面全局宽度单独思考。2.3 先建容器单位才会生效容器查询单位不能单独使用。你必须先把某个祖先元素定义成查询容器它下面的后代才能使用cqw、cqi这些单位。最简配置.card { container-type: inline-size; }container-type: inline-size的意思是让该元素成为查询容器并以它的内联方向尺寸作为查询基准。它不会强制容器必须有高度所以是默认推荐值。如果写container-type: size容器还需要显式设置高度否则高度会塌陷日常使用容易踩坑。2.4 一个卡片组件示例字号和内边距都跟着容器走假设你有一个媒体卡片可能被放在侧边栏也可能放在主内容区。以前你需要通过媒体查询或者.sidebar .card这种嵌套选择器来覆盖。现在可以直接让卡片内部使用容器查询单位.media-card { container-type: inline-size; } .media-card__title { font-size: clamp(1.1rem, 4cqi, 2.4rem); } .media-card__body { padding: 2cqi; }当卡片宽度为 400px 时4cqi就是 16px当卡片宽度为 800px 时4cqi就是 32px。clamp()又给字号设置了上下限避免过小或过大。这种写法让组件本身具备了“容器感知”能力不再依赖页面级断点。2.5 容器单位最容易踩的三个坑第一个坑忘了设置container-type。没有查询容器cqi、cqw就直接失效。查问题时先看最近的祖先元素有没有加这个属性。第二个坑容器自身用容器查询单位。查询容器只能作为基准它不能拿自己作为参照。如果你想给容器自身设置宽度要用百分比、视口单位或者普通长度单位。第三个坑容器高度由内容撑起来时不要用高度方向的cqh或cqb。高度取决于内容内容又依赖单位容易形成循环依赖。因此绝大多数场景优先用cqi。注意容器查询单位只对查询容器的后代生效。如果你写了一个组件但页面里没有任何带container-type的祖先它不会自动退化成视口单位结果就是声明无效。3. 字体类新单位lh、rlh、cap、ic 不是摆设但也别全用3.1 lh 和 rlh把间距与行高绑定lh单位是“当前元素行高”的百分比1lh等于当前元素计算出来的行高。rlh则是根元素行高的百分比。这个单位最大的价值是让垂直间距和文字排版节奏保持一致。以前你为了“段落之间空一行”通常写margin-bottom: 24px但字号和行高一变24px 的视觉比例就乱了。如果用1lh间距会始终等于一行的高度.article p { line-height: 1.7; margin-bottom: 1lh; }这样无论字号怎么调整段落间距始终是“一行”排版比例不会崩。rlh更多用于全站基础组件可以把全局间距统一锚定在根元素的行高上。需要注意不要把lh用在line-height属性本身上这会产生循环定义。它更适合margin、padding、top、bottom这类长度属性。3.2 cap让按钮高度跟大写字母对齐cap单位相对的是当前字体的大写字母高度也就是从基线到大写字母顶部的距离。这个值比em更能反映“文字实际看起来多高”。常见痛点按钮高度设成40px视觉上总觉得文字偏上偏下。用cap参与高度计算可以让按钮高度紧密贴合实际字符高度.button { height: calc(2em 0.5cap); padding: 0.25cap 1em; }这不能解决所有对齐问题但在制作精细按钮、图标与文字混排时比纯靠em、固定像素要准。3.3 ic在中文排版里值得留意ic单位是表意字符的推进宽度简单理解就是“一个汉字大概占多宽”。在中文环境里这是一个很实用的度量概念。比如你想限制某段文本显示大约 20 个字符宽.chinese-excerpt { width: 20ic; }在常见中文字体下1ic会落在 1em 附近但会根据具体字体修正。比起直接写20emic在理论上更贴合“汉字字符宽度”的语义。目前它的浏览器支持还不算广泛落地前需要先确认目标环境并写好回退。3.4 字体类单位的坑字体加载、单位值不稳定字体类单位有一个共同问题它们依赖字体文件加载完成后的度量值。如果页面使用了 web font在字体加载前后lh、cap、ic对应的像素值可能会变化导致布局出现轻微抖动。缓解办法是字体显示策略用font-display: swap时提前想到布局偏移对关键排版区域先设置足够接近的回退值上线前在真实网络环境里检查而不是只看本地。这类单位适合“锦上添花”不建议在一开始就把所有间距都迁移到lh上。先在一个模块试看效果再推广。4. 几十个单位里真正值得进项目的就这几类4.1 四个判断维度兼容性、可回退、收益、复杂度面对一堆新单位做技术选型不需要把每个都研究透。我会用四个问题来判断目标浏览器支持吗不支持时有无简单回退用完之后代码是更简单还是更复杂会不会引入布局循环、字体抖动等副作用基于这四个问题我给出现阶段的个人判断svh、dvh值得先大规模替换100vhcqi、cqw值得在组件化项目里逐步引入cqh、cqb要谨慎确认容器高度是明确值再用lh、rlh适合排版类模块先小范围验证cap、ic属于特定场景按需使用其他零散单位暂时观望。4.2 一张速查表推荐度、典型场景、回退方案下面这张表可以直接复制到项目文档里作为团队参考单位类型推荐度典型场景回退方案dvh/svh高移动端全屏、底部操作栏、弹层先写100vh再写新单位cqi/cqw中高卡片内字号、间距、组件级响应式固定rem值或媒体查询cqh/cqb中容器高度明确时的内部布局百分比或固定高度cqmin/cqmax中低特殊比例控制不推荐优先使用lh/rlh中垂直节奏、段落间距rem倍数cap中低按钮高度、图标文字对齐em或固定像素ic中低中文字符宽度控制、古籍/中文版式em或固定宽度vi/vb低多书写模式场景物理单位vw/vh注意上表是“现阶段通用实践”下的经验排序不是规范要求。你的项目如果只跑在最新的 Chromium 内核上推荐度可以整体上调如果还要兼容旧版本 WebView就要保守得多。4.3 clamp cqi组件级流式字号的最好组合这是目前我认为最值得抄的一段实践.card { container-type: inline-size; } .card-title { font-size: clamp(1.25rem, 4cqi 0.5rem, 2.75rem); }它的价值在于字号不再只依赖页面视口而是跟着最近的组件容器走同时用clamp()限制了最小值和最大值避免容器极窄或极宽时字体失控。如果是全站统一字号还可以抽成 CSS 变量:root { --title-size: clamp(1.25rem, 4cqi 0.5rem, 2.75rem); } .card-title { font-size: var(--title-size); }不过要注意CSS 变量是惰性计算的cqi只在真正用到变量、且元素有查询容器的场景下才生效。别把--title-size放在根元素然后期望全站所有标题都基于各自容器算这个需要分别在不同组件里重新声明。4.4 这些场景先别引入旧项目、低版本 WebView、像素级还原不是所有项目都适合追新单位。旧项目全局已有大量固定像素和断点引入新单位会造成两套单位混用维护成本反而增加。建议新页面、新模块先试。低版本 WebView很多 App 内嵌 WebView 的浏览器内核升级很慢dvh、cqi都可能不被识别。落地前必须确认最低支持版本。像素级还原如果设计稿要求精确到像素容器查询单位会让设计验收变难。不是不能用而是你要先说服团队成员接受“动态字号”这件事。4.5 长期价值从“页面级响应式”进入“组件级响应式”这些新单位真正改变的事情不是“多了几个单位”而是把响应式的衡量基准从“页面视口”扩展到了“组件容器”。过去做一套组件要在页面级考虑它在不同宽度下怎么变现在组件可以基于自身容器响应复用性大幅提高。这就是我认为动态视口单位和容器查询单位最核心的长期价值让组件成为一个真正自洽的单元。5. 落地排查新单位不生效时按这个顺序找问题5.1 先确认现象和输入如果新单位写了但没效果先别急着怀疑浏览器。第一步看现象是完全没有生效还是部分浏览器不生效是字号没变还是高度错乱是始终使用兜底值还是布局抖动再看写法。CSS 单位名是大小写敏感的规范的视口单位、容器查询单位都是小写。写100DVH、4CQI在大部分浏览器里不会被识别整条声明会失效。还要检查calc()里的空格calc(100dvh - 80px)中间的减号两侧必须有空格否则声明非法。5.2 再查浏览器环境和支持情况新单位最大的敌人不是语法而是浏览器版本。建议用supports做能力检测supports (height: 100dvh) { .module { height: 100dvh; } }如果条件不成立可以给一个明确回退。排查时也可以在开发者工具控制台里快速验证CSS.supports(height, 100dvh)返回true再排查别的问题返回false说明当前浏览器根本不支持这个单位。5.3 再查容器和布局上下文这一步主要针对容器查询单位。很多“不生效”的案例根本不是浏览器不支持而是没有给祖先元素设置container-type;设置的是container-type: size但容器没有明确高度导致容器高度塌陷使用cqh但容器高度是由内容撑开的产生了循环依赖想把容器查询单位用在容器自身但规范只允许用于后代。解决办法是先建一个最小复现页面只放一个容器和一个子元素跑通后再套进真实组件。5.4 最后查构建产物和工具链如果你使用了 PostCSS、CSS Modules、cssnano 这类工具还要检查构建产物里是否还保留着新单位。正常情况下压缩工具不会把合法单位删掉。但有些团队配置了比较激进的 PostCSS 插件比如自动降级版本、自动加前缀的插件可能会影响未知单位。排查时直接看最终打包出来的 CSS 文件搜索dvh或cqi确认它真的存在并且没有被某个插件转换成别的值。排查顺序总结成一张表排查层常见问题验证方式写法单位拼写错误、calc空格问题查看源代码与 Network 返回的 CSS浏览器版本过旧、WebView 不支持CSS.supports()检测容器没设置container-type、容器高度塌陷DevTools 查看容器尺寸与 Computed 值构建PostCSS 插件错误处理新单位查看打包后的 CSS 产物5.5 一个最小实验模板如果你想验证新单位在当前项目里能不能用建议先做下面这个最小实验div classcontainer div classitemHello CSS/div /div.container { container-type: inline-size; width: 400px; border: 1px solid blue; } .item { font-size: 5cqi; padding: 2cqi; border: 1px solid red; }在浏览器里打开后把.container的宽度改成 800px观察.item的字号是否跟着变大。如果这个实验正常说明浏览器支持、容器配置也正确。如果没变说明问题出在环境或工具链上。注意先跑通一个最小案例再做批量替换。一次引入太多新单位出了问题很难定位是哪一层造成的。CSS 新单位不是“学会所有新语法”而是“知道哪些能真正改变工作流”。我的建议是先从移动端100vh这个最痛的问题开始把全屏模块替换成svh/dvh再找一个卡片组件试着用cqi控制内部字号。你不需要记住全部几十个单位但这两组单位带来的体验变化值得认真用起来。