ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CSS新单位实战指南:dvh、容器查询与排版单位详解

CSS新单位实战指南:dvh、容器查询与排版单位详解 如果你在移动端浏览器上调试过 H5 页面大概率见过这个场景页面根节点设了height: 100vh滚动几下之后底部被浏览器工具栏挡住或者输入框唤起键盘时固定按钮被顶出屏幕。老办法无外乎监听resize、拿window.innerHeight手动设置高度、再用onOrientationChange做一次补救。这套 JavaScript 补丁逻辑本质上是在替 CSS 的历史实现问题买单。其实 CSS 自己已经给出了答案。这几年 CSS 规范里新增的尺寸单位连同各类相对单位一起已经有几十个之多。只是平时业务里常用的还是px、rem、vw、vh那几位很多新单位甚至连名字都没怎么听说过。按标题的说法这几十个单位里确实有不少“凑数”成员真正值得“屎里淘金”淘出来用的我判断主要是三类动态视口单位dvh/svh/lvh、容器查询单位cq*、以及排版向的lh/ic等。这篇文章帮你把这三类以及其它值得知道的新单位全部过一遍它们解决什么问题、和旧单位有什么区别、怎么在真实项目里落地、遇到兼容性怎么兜底。最后给一个移动端示例页和一份排查清单。建议先收藏再慢慢看。1. 这篇文章要解决的问题为什么突然需要关注 CSS 新单位先说一个事实CSS 的尺寸单位体系其实是一个长期被低估的领域。很多人学 CSS 时只记住了px是绝对单位、em和rem是相对单位、vw/vh是视口单位然后就开始写页面了。但前端开发的场景早就不是“一个 PC 浏览器窗口”这么简单了。现在的真实环境是同一个页面会在小屏手机上滑来滑去会在 iPad 分屏里被压缩到一半宽度会被嵌入到第三方页面的 iframe 里还会在组件库里被复用进不同宽度的侧边栏、弹窗和内容区。当布局的参照物从“浏览器窗口”滑向“组件的父容器”“文本自身的行高”“字符本身的字形宽度”时CSS 单位就需要有更细的刻度。这就是近几年新单位大量出现的原因它们不是给 CSS 增加“新玩法”而是在给不同的布局需求提供新的参照系。普通开发者最容易遇到的痛点有三个第一个是移动端高度问题。100vh在不同浏览器、不同工具栏状态下表现不一致导致页面底部被遮挡、全屏容器溢出。这个问题存在多年直到svh/lvh/dvh出现才有原生解法。第二个是组件复用问题。过去我们用媒体查询让页面响应视口但组件并不知道自己所在的容器有多宽。一个卡片在侧边栏里要窄版布局在内容区要宽版布局媒体查询根本写不对。第三个是文本排版问题。首行缩进、行数裁切、输入框宽度估算看起来都是小事但用em、px去估算总是不精确。lh、ic、ch这些单位才是从排版语义出发的答案。什么人最该读这篇文章我的判断是做移动端 H5 和混合 App 的前端应该重点读第 3 章和第 4 章做组件库、设计系统和业务中台的前端应该重点读第 4 章和第 9 章经常处理文章排版、文档站点、富文本渲染的开发者应该重点读第 5 章。如果你只是写简单的后台管理页面那这篇文章至少能帮你建立一个“新单位存在但不必全用”的判断力避免以后在同事代码里看到dvh时一脸茫然。2. 先看清全貌CSS 尺寸单位到底分几类在深入讲单个单位之前有必要先把整个体系铺开看。CSS 里的尺寸单位数量并没有官方精确统计但把标准里出现过的相对单位列出来确实有几十个。它们其实可以分成八个大类类别单位参考基准典型用途绝对单位px, pt, cm, mm, in, pc物理尺寸或参考像素边框、固定尺寸字体相对单位em, rem元素字体大小、根字体大小字号、间距字符相对单位ex, ch, ic, cap字符特征文本语义尺寸行高相关单位lh, rlh元素行高、根元素行高行数裁切、垂直节奏视口相对单位vw, vh, vmin, vmax视口宽高全屏布局动态视口单位svw, svh, lvw, lvh, dvw, dvh小/大/动态视口移动端适配逻辑视口单位vi, vb视口内联/块方向多语言、竖排容器查询单位cqw, cqh, cqi, cqb, cqmin, cqmax查询容器组件自适应这张表的价值在于你不需要把所有单位都记下来只需要知道每一个新单位背后都对应一个“参照系”。px的参照系是屏幕物理像素rem的参照系是根元素字体大小vw的参照系是视口宽度cqw的参照系是容器查询容器的宽度lh的参照系是当前元素的行高。2.1 绝对单位与相对单位的本质区别绝对单位看起来简单但实际上也很少有人全部用对。pt是印刷行业的点在屏幕上会被浏览器按 1pt 1/72 英寸换算cm、mm、in在屏幕上同样会换算成像素。绝大多数 Web 开发场景只需要px其它绝对单位在打印样式表里才会更常用。记住一个原则绝对单位适合“无论屏幕怎么变这个尺寸都不该变”的场景比如 1px 边框、2px 圆角。相对单位才是 CSS 真正灵活的底盘。em随着元素自身字体大小变化适合做组件内部与字号成比例的间距rem随根元素字体大小变化适合做全局统一的间距和字号系统vw/vh随视口变化适合做全屏页面。新单位本质上都是这两类思路的延伸只是参照物更细了。2.2 新单位不是“新写法”而是新的参照系如果把 CSS 单位体系当成一个坐标系旧坐标只有“屏幕像素”和“字体大小”两个刻度新坐标则引入了更多刻度浏览器地址栏高度、容器宽度、字符宽度、行高。dvh的参照系是动态变化的视口cqi的参照系是最近的容器查询容器的内联尺寸ic的参照系是全角字符的字形宽度lh的参照系是当前行高。理解这个逻辑之后很多新单位就不需要死记硬背。你只需要在面对一个布局问题时问自己我希望这个元素跟随谁变化跟随视口用vw/vh/dvh跟随容器用cq*跟随文字用em/ch/ic/lh。这个问题想清楚新单位的选择自然就出来了。3. 动态视口单位 dvh / svh / lvh移动端高度适配的正确姿势如果说新单位里只能挑一个最先用我一定会选dvh。它解决的问题年代久远、覆盖面广而且代码改动量极小。3.1 100vh 为什么在移动端不可靠问题根源在移动端浏览器的动态工具栏。在 iOS Safari、Chrome Android 上浏览器顶部地址栏和底部工具条会随着滚动收起或展开。工具栏收起时可视区域变高工具栏展开时可视区域变矮。问题在于100vh到底应该等于“最小的可视高度”还是“最大的可视高度”历史上不同浏览器给的答案不一样。这就导致一个经典 bug页面设置height: 100vh之后在工具栏展开状态下底部一部分被工具条遮住滚动到页面底部时又可能出现几像素到几十像素的空白间隙。为了修复这个问题开发者只能写 JavaScript 补丁监听resize事件实时读取window.innerHeight再写回 CSS 变量。这个方案能用但会导致布局抖动、滚动不流畅而且代码维护成本很高。3.2 三个新单位的区别与选择CSS 规范直接把“会变的视口”拆分成了三种状态svh是 small viewport height小视口高度指工具栏完全展开时可视区域的高度。它是所有状态下最小的那个高度。lvh是 large viewport height大视口高度指工具栏完全收起时可视区域的高度。它是所有状态下最大的那个高度。dvh是 dynamic viewport height动态视口高度会随着浏览器工具栏的展开和收起实时变化。什么时候用哪个可以按这个口诀判断场景一页面内容希望始终完整可见不因工具栏出现而被遮挡用svh。比如全屏活动页、发布页。场景二页面希望保持稳定不因为工具栏收展而产生高度跳动用lvh。比如登录页、信息展示页。场景三页面希望尽可能利用当前真实可用高度并且能接受随工具栏变化而动态调整用dvh。这是最接近老思路window.innerHeight的方案也是大多数移动端 H5 的首选。3.3 实际案例底部操作栏与全屏页面下面是一个移动端常见的“全屏容器 底部固定操作栏”结构用dvh来布局!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title动态视口单位示例/title style /* 页面整体高度优先用 100vh 回退支持 dvh 的浏览器用 100dvh 覆盖 */ .page { height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; background: #f5f6fa; } .page__main { flex: 1; overflow-y: auto; padding: 16px; } .page__footer { height: 72px; flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; background: #fff; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06); } .btn { width: 120px; height: 40px; border: none; border-radius: 20px; background: #2d6cdf; color: #fff; font-size: 14px; } /style /head body div classpage main classpage__main p页面主体。在移动端浏览器中上下滚动底部按钮栏不会再被工具栏遮挡。/p /main footer classpage__footer span合计¥ 199.00/span button classbtn提交订单/button /footer /div /body /html这里真正容易踩坑的地方是回退顺序。100vh写在前面100dvh写在后面这样不识别dvh的浏览器会保留100vh而支持dvh的浏览器会用后面的值覆盖。如果顺序写反不识别dvh的浏览器会直接丢弃整条声明导致没有高度可用。这一点在后面讲兼容性时还要重点强调。从实际体感看dvh在移动端带来的改善是最直观的页面不再闪跳滚动到底部也不会露白。即使你的项目暂时不需要全屏页面只在一个弹窗或者底部面板里使用dvh也足够值回学习成本。4. 容器查询单位 cqw / cqh / cqi / cqb让组件真正跟随容器如果说dvh是“把视口变量化”容器查询单位就是把“容器变量化”。这是组件化开发里非常关键的一步。4.1 媒体查询做不到的事在容器查询出现之前我们的响应式手段基本只有媒体查询。媒体查询的问题是它只能感知浏览器视口不能感知元素自己的父容器。所以会出现一种尴尬场景同一个按钮组件在侧边栏和主内容区里明明宽度差距很大却因为视口没有变化而没法分别调整样式。用媒体查询强行做就得按视口分段在某个视口宽度下侧边栏里的按钮可能已经挤得很难看了主内容区的按钮还空荡荡的。容器查询解决的是这个问题它让组件可以基于最近的“查询容器”的尺寸来应用样式。CSS 容器查询单位的含义如下cqw是容器查询容器宽度的 1%cqh是容器高度的 1%cqi是容器内联方向尺寸的 1%cqb是容器块方向尺寸的 1%cqmin是cqw和cqh中较小值cqmax是较大值。在实际开发中cqi用得最多因为大多数布局关注的是容器宽度而内联方向在水平书写模式下就是宽度方向。4.2 容器查询单位怎么用要使用容器查询单位第一步是给父元素声明它是一个查询容器.card-list { container-type: inline-size; container-name: product-card; }container-type: inline-size表示只跟踪容器内联方向的尺寸变化这样性能更好。声明之后容器内的所有后代元素都可以使用容器查询单位。container-name是可选的但建议加上方便在容器查询条件中明确指定作用对象。第二步是在容器内部使用cq*单位。比如.product-card { padding: 2cqi; } .product-card__title { font-size: clamp(14px, 3cqi, 22px); } .product-card__desc { font-size: 1.2cqi; }这段 CSS 的含义是卡片的内边距、标题字号、正文字号都跟随容器宽度变化。容器越宽内边距越大字越大容器变窄所有尺寸等比收缩。这里真正有价值的是“等比缩放”这个能力——它比媒体查询逐级切换更平滑也更接近设计系统中“弹性组件”的预期。4.3 组件复用场景演示假设你有一个产品卡组件需要同时放在宽侧边栏和窄内容区里。传统方案是写两套媒体查询现在只需要/* 组件无感知随所在容器自适应 */ .product-card-wrap { container-type: inline-size; } .product-card { padding: clamp(8px, 2cqi, 24px); border-radius: clamp(4px, 1cqi, 16px); } .product-card__cover img { height: clamp(120px, 30cqi, 320px); object-fit: cover; } .product-card__title { font-size: clamp(12px, 4cqi, 24px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }把这段样式配合 Flex 或 Grid 布局使用同一个组件在不同容器里会自动呈现“窄版紧凑、宽版舒展”的形态。这种能力给组件库开发带来了很大便利组件不再关心自己被放到哪里只需要依赖父容器尺寸。容器查询单位也比纯容器查询媒体块更轻量因为它直接把尺寸计算拿来做单位不需要再写container条件块。需要注意容器查询单位必须在查询容器内部有效。如果父元素没有声明container-typecqi会退化或者不生效。这是新手最容易踩的坑。5. 排版单位 lh / rlh / ch / ic / ex文本对齐与裁切的新解法视口和容器单位解决的是盒子模型问题排版单位解决的则是“文本本身”的度量问题。这一组单位可能没有dvh那么吸引眼球但在文章排版、文档站点、多语言界面里非常实用。5.1 用 lh 做行数裁切lh是 line-height 的缩写1lh 等于当前元素line-height的计算值。rlh则是根元素html 元素的line-height计算值。过去我们要实现“文字不少于两行、多于三行就裁切”的效果只能用max-height加一个手算的像素值或者 em 倍数。现在可以直接用lh精确匹配行高.article-summary { line-height: 1.8; max-height: 3.6lh; /* 3.6lh 2 行因为 1.8 * 2 3.6 */ overflow: hidden; } .notice { line-height: 24px; max-height: 48px; /* 兜底方案 */ max-height: 2lh; /* 支持 lh 的浏览器自动用 2lh 覆盖 */ overflow: hidden; }这里的关键在于lh参照的是当前元素的行高不是字体大小。如果行高是通过倍数设置的比如line-height: 1.5那么 1lh 就等于1.5 * font-size。这种语义比用 em 去估算行数清晰得多。不过要提醒一句lh做多行裁切还是需要配合overflow: hidden它本身不会产生省略号。如果要做“两行省略号”效果一般还是搭配-webkit-line-clamp一起用。lh更适合做精确的行数限制和垂直节奏控制。5.2 用 ic 处理中文缩进与对齐ic是 ideographic character unit 的缩写表示一个全角表意字符的宽度。对于中文、日文、韩文这类文字一个汉字或者日文假名的字形宽度通常就是 1ic。这个单位非常有用。最典型的场景是首行缩进。以前我们写text-indent: 2em但 em 是相对字体大小的如果字体里混入了英文或数字缩进会不精确。用text-indent: 2ic则无论字体大小如何、是否混排都保证缩进两个全角字符的宽度.post-content { font-size: 16px; line-height: 1.8; } .post-content p { text-indent: 2ic; /* 首行缩进两个汉字宽度 */ }另一个场景是文本对齐。中文界面上经常需要把“姓名”“手机号”“地址”这样的标签对齐以前要么写死宽度要么用 table。如果标签都由中文组成用width: 2ic或者min-width: 2ic就能准确占两字宽度对齐效果非常干净。5.3 ch、ex、cap 的适用边界ch是字符“0”的宽度。它适合做输入框宽度估算。比如验证码输入框如果知道验证码是 4 位数字可以设width: 4ch输入框宽度就能比较贴合 4 位数字的视觉宽度。但要注意如果输入内容是全角字符ch就会显得不够用此时应该改用ic。ex是小写字母 x 的高度。它适合做与字母中线对齐的微调比如小徽标、上标、下拉箭头的尺寸。但 ex 的值受字体影响很大不同字体差异明显不建议作为主要尺寸单位。cap是大写字母的高度单位语义是“当前字体中大写字母的高度”。它在规范里已经出现但浏览器支持度还不够理想生产环境暂时不建议使用知道有这个东西就行。这组排版单位的使用原则是什么时候该用lh/ic/ch什么时候继续用px/em/rem我的判断是当尺寸必须和文本的排版语义保持一致时优先用排版单位当尺寸只是视觉装饰、与文本内容无关时用回px/rem更稳。比如分割线高度用 1px 完全可以不需要为了用新单位而用新单位。6. 逻辑视口单位 vi / vb 与其它零碎单位除了上面三类重点单位还有几个单位值得简单扫一眼。它们的使用频率不算高但遇到特定场景会很省力。vi是 viewport inline指视口在内联方向上的尺寸的 1%。vb是 viewport block指视口在块方向上的尺寸的 1%。这两个单位引入了 CSS 逻辑属性的概念它们不关心物理方向而关心书写方向。在默认的水平书写模式下vi约等于vwvb约等于vh。但如果页面使用了竖排文字比如writing-mode: vertical-rl内联方向变成了垂直方向vi就会等于视口高度方向上的 1%。竖排场景写起来是这样的.vertical-note { writing-mode: vertical-rl; width: 80vi; /* 竖排时宽度跟随视口内联方向 */ height: 90vb; /* 高度跟随视口块方向 */ }这个能力在多语言排版、日式竖排页面、电子期刊类项目里价值很大。普通业务页面基本用不上但你至少要知道如果你看到vi/vb它不是打字错误而是逻辑属性思路下的视口单位。另外动态视口单位还有宽度版本svw/lvw/dvw它们的原理和svh/lvh/dvh完全一样只是方向换成了宽度。移动端宽度很少动态变化所以实际使用率不高但在处理横屏、折叠屏等场景时仍然有位置。容器查询单位里还有cqh、cqb、cqmin、cqmax。其中cqh和cqb需要容器有明确的、可计算的高度才会生效这在普通流式布局里不容易用上cqmin和cqmax适合做“容器宽度和高度取小/取大”的复杂响应。日常项目里建议重点关注cqi次重点cqw其它几个了解即可。总体上看逻辑视口单位和尺寸单位变体都属于“备用工具箱”。它们不承担主要布局任务但总会在某个国际化项目、竖排文档或特殊组件里发挥不可替代的作用。7. 综合示例一个移动端任务页同时用上这些新单位说了这么多概念不如直接上一个完整示例。下面这个页面是一个典型的移动端 H5 任务详情页同时用到了dvh、容器查询单位、lh和ic。代码可以直接保存为 HTML 文件在浏览器里打开。7.1 页面结构与代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleCSS 新单位综合示例/title style * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, PingFang SC, Hiragino Sans GB, Microsoft YaHei, sans-serif; background: #f0f2f5; } /* 1. 动态视口单位整页高度 */ .page { height: 100vh; /* 回退方案 */ height: 100dvh; /* 动态视口高度 */ display: flex; flex-direction: column; overflow: hidden; } .page__header { padding: 16px 16px 12px; background: #fff; } .page__main { flex: 1; overflow-y: auto; padding: 16px 12px 30px; } /* 2. 容器查询单位卡片内边距和字号跟随容器宽度 */ .card-list { container-type: inline-size; container-name: cardList; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; } .card { background: #fff; border-radius: 4cqi; padding: 4cqi; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); } .card__title { font-size: clamp(15px, 3.5cqi, 22px); line-height: 1.5; font-weight: 600; } .card__desc { margin-top: 6px; line-height: 1.7; max-height: 3.4lh; /* 约等于 2 行结合 overflow 裁切 */ overflow: hidden; } .card__quote { margin-top: 10px; padding-left: 10px; border-left: 3px solid #2d6cdf; text-indent: 2ic; /* 首行缩进两个中文字符宽度 */ color: #666; font-size: 13px; } .page__footer { flex-shrink: 0; height: 68px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; background: #fff; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06); } .btn { height: 40px; padding: 0 20px; border: none; border-radius: 20px; background: #2d6cdf; color: #fff; font-size: 14px; cursor: pointer; } /style /head body div classpage header classpage__header h1CSS 新单位综合示例/h1 /header main classpage__main div classcard-list div classcard h2 classcard__title动态视口布局/h2 p classcard__desc 页面外层使用 100dvh 布局移动端浏览器的工具栏展开与收起时 页面高度会跟随真实可用区域变化底部不会露出遮挡或空白。 /p p classcard__quote 卡片内部使用 cqi 作为尺寸单位文字和内边距会随容器宽度等比变化。 /p /div div classcard h2 classcard__title容器查询单位/h2 p classcard__desc 卡片容器的宽度不同cqi 的值也不同。放在宽容器里卡片内边距变大 放在窄容器里内边距自动变小。 /p p classcard__quote 这种能力让组件可以在不同布局区域里保持合适的视觉节奏。 /p /div /div /main footer classpage__footer span任务完成率 80%/span button classbtn提交任务/button /footer /div /body /html7.2 关键点拆解这个示例里有几处值得注意的设计第一height: 100vh写在100dvh前面。这是渐进增强的标准写法。旧浏览器读到第一行使用100vh支持dvh的浏览器读到第二行覆盖成动态视口高度。顺序绝对不能反过来。第二.card-list声明了container-type: inline-size之后.card内部的所有cqi单位才有作用。如果你删掉这一行border-radius: 4cqi和padding: 4cqi会失效卡片视觉上会变得很挤。第三.card__desc的max-height: 3.4lh是配合line-height: 1.7计算出来的。1.7 * 2 3.4所以3.4lh正好等于两行行高的高度超过两行就裁掉。如果字体加载后行高发生变化lh计算值也会同步变化不会出现用 em 估算时那种“行数算不准”的问题。第四.card__quote使用了text-indent: 2ic。在中文环境下这等价于缩进两个汉字宽度。这和最传统的text-indent: 2em效果接近但当卡片字体大小随cqi变化时ic的语义依然稳得住因为它是相对字形宽度的。这个页面的整体效果是外层布局跟随动态视口内部卡片跟随容器宽度文本行数跟随行高中文缩进跟随字形宽度。每一层都用了“适合自己的参照系”这就是新单位组合起来使用的基本模式。8. 新单位常见问题与排查思路新单位使用过程中最麻烦的不是记不住名字而是“为什么我写了但没效果”。下面的排查表整理了最常见的几类问题可以直接对照找原因。问题现象可能原因排查方式解决方案100vh 在 iOS 上底部被浏览器工具栏遮住vh 在不同浏览器中取的是不同视口状态在 iOS Safari 打开页面滚动观察页面底部改为100dvh并把100vh写在前面作为回退写了cqi/cqw但样式不生效父元素没有设置container-type打开开发者工具 Elements 面板查看父元素是否显示 Container 标记给父元素加上container-type: inline-sizecqi值被浏览器当成无效值浏览器版本过旧不支持容器查询单位在 caniuse 搜索 container query units用旧单位回退或将组件库升级到现代浏览器支持范围max-height: 2lh裁切行数不准确line-height设置的是倍数但实际字体行高受字体影响在 Computed 面板查看 line-height 计算值显式设置line-height: 24px这样的固定值再计算 lhtext-indent: 2ic没有明显缩进当前字体没有全角字形宽度数据或字体未加载检查字体加载状态和浏览器兼容性回退到text-indent: 2em用supports做能力检测新单位在旧浏览器里导致整行声明被丢弃浏览器不识别该单位时会丢弃整条声明查看 Console 是否有 Style 解析警告将旧单位写在前面新单位写在后面形成回退链dvh导致高频变化时布局抖动浏览器工具栏收展引发多次重排用 Performance 面板录制滚动过程高频变化场景改用svh或
返回列表