12现代 CSS 布局
2022 年之后,CSS 布局悄悄迎来了四件革命性武器:容器查询(让组件响应「自己的父容器」而非视口)、
:has()(终于能选「父元素有某个子元素的元素」)、subgrid(让子网格继承父网格轨道)、
还有 @scope(CSS 终于有了局部作用域)。这一课把它们四个一次讲清,
再加上 :is() / :where() 与「逻辑属性」两个常被忽视的好搭档——看完你就能写「可复用」的真·组件级 CSS。
一、容器查询 @container:组件自适应的开始
媒体查询(@media)的经典痛点:它只看视口,不看组件自己的父容器。
一个卡片放在宽屏里看着很好,但被丢进窄侧栏就「越界」,媒体查询救不了它——
因为视口没变。容器查询(@container,Chrome 105+、Safari 16+、Firefox 110+)应运而生:
让元素响应自己的父容器尺寸,写出真正可复用的组件级 CSS。
| 值 | 行为 | 副作用 | 典型场景 |
|---|---|---|---|
| normal(默认) | 不是容器,子孙不能用 cqw / cqh | 无 | 绝大多数场景 |
| size | 行内 + 块方向尺寸都可查 | 强制 display:block,不能 inline / flex / grid | 纯尺寸场景,不常用 |
| inline-size | 只让行内方向尺寸可查 | 几乎无副作用,最常用 | 卡片、文章、产品单元——绝大多数组件 |
| block-size | 只让块方向尺寸可查 | — | 竖排文字、上下拉拽场景 |
.cq-card 的宽度拖动到 350px 以下,看右侧卡片跟着「塌」成竖排宽容器(≈520px)
容器 ≥400px 时,标题与正文横排
窄容器(≈280px)
容器 <400px 时,自动竖排
container-name 是可选的,但加上后
@container 名 (条件) { ... } 才能精准指定「查哪个容器」,避免大页面里多个容器嵌套时的混乱。
嵌套场景强烈建议每个容器都起名字。二、容器查询单位 cqi / cqw / cqh
@container 让样式「按容器尺寸切换」,但常常我们想更细——
「字号跟着容器宽度连续缩放」。这时要用容器查询单位:
cqi(容器 inline-size 的 1%)、cqw(容器 width 的 1%)、
cqh(容器 height 的 1%)。它们就是 vw / vh 的容器版。
cqw≡cqi:在 LTR 横排文字下完全等价,写法按习惯选。cqh:容器高度的 1%,适合「按父高度设字号」的奇景。clamp(min, 容器相对值, max)套上去就能做「流式字号」——标题在小卡片里小字,在大卡片里大字。
flu-wrap 右下角拖到最窄(约 220px),标题字号缩到接近 1rem;拖到最宽(约 800px),放大到 ~2rem。注意:字号跟随容器宽度,不是浏览器窗口宽度容器里看窗外
拖动右下角调整容器宽度——标题字号跟随父容器缩放。窗口本身不动。
font-size: clamp(最小, cqi * N, 最大),N 一般取 3 ~ 6。
想更精细:把字号、padding、border-radius 全都用 cqi 写,整张卡片就像「活的」一样随父容器呼吸。三、:has() 选择器:父选子的反向选择
CSS 选择器历来是「父找子」单向的——你没法写「如果我有某个孩子,就选我」。
:has()(Chrome 105+、Safari 15.4+、Firefox 121+)破天荒打破了这个规则:
它让父元素可以根据自己子孙的状态被选中。从此「表单非法时整表单红框」「卡片有图就两栏」「文章含表格就改版式」——
这些过去只能 JS 监听的事情,一行 CSS 搞定。
| 场景 | 写法 | 效果 |
|---|---|---|
| 表单有非法输入 | form:has(input:invalid) { ... } |
整表单红框,零 JS |
| 卡片含图 | .card:has(img) { ... } |
有图卡片用两栏,没图卡片用一栏 |
| 列表里第一个被悬停 | li:hover:first-child { ... } 的反向 |
可配合 :has(:hover) 高亮整组 |
| 父级响应孙子状态 | .post:has(code) { ... } |
文章含代码块时加个「含代码」徽标 |
| 紧邻选择升级 | h2:has(+ p) { ... } |
有紧随其后的段落时给标题加间距 |
required 改成空字符串让所有字段都合法,看红框消失@supports not selector(:has(*)) 包一层旧浏览器降级样式——
旧浏览器完全看不到红框(也不会出错),新浏览器则享受完整体验。这是「渐进增强」的现代写法。四、@scope:CSS 的局部作用域
写组件久了最痛的事:全局 CSS 互相污染。
BEM、命名空间前缀都是「土办法」——真正的解法是@scope(Chrome 118+、Safari 17.4+):
给一块 CSS 划定作用域,里面的选择器只匹配作用域根的子孙,不会污染外面,外面也进不来。
| 浏览器 | 最低支持版本 | 状态 |
|---|---|---|
| Chrome / Edge | 118+ | 已支持(含 :scope、to 语法) |
| Safari | 17.4+ | 已支持 |
| Firefox | 暂未发布 | 开发中,about:config 可手动开启 layout.css.scoped-styles.enabled |
| 替代方案 | BEM / CSS Modules / Shadow DOM | 用命名约定或构建工具隔离,绕开全局污染 |
h4 不会影响左侧独立 h4。把 @scope 那块注释掉,再看两个 h4 都被染红——污染了卡片 A(被 @scope 保护)
卡片 A 里的 h4 会被 @scope 规则染色,但外部的 h4 不受影响。
卡片 B(独立作用域)
这是另一张卡片,作用域相互独立——互不污染。
@scope 兼容性还很窄(Chrome 118+ / Safari 17.4+,Firefox 暂未发布)。
在生产环境单独使用风险大,建议作为渐进增强:核心样式照旧用 BEM 或 CSS Modules,
@scope 用来优化「重复样式块」或「局部主题色覆盖」等增强体验。五、subgrid:让子网格继承父网格轨道
CSS Grid 嵌套有个多年未解的痛点:父网格三列时,子网格的列宽不会自动沿用父网格——子网格得自己再算一遍。
subgrid(Chrome 117+、Safari 16+、Firefox 71+)让子网格沿用父网格的轨道尺寸,
写嵌套对齐终于不用算算术了。
subgrid 完美对齐父三列;下方改成 grid-template-columns: 1fr 1fr 立刻错位——子网格自己设的列数和父网格不一致就「各说各话」父网格(3 列):A · B · C
下面这组:子网格写 1fr 1fr,看错位
六、:is() / :where() 选择器
写选择器时常有这种痛苦:h1, h2, h3, h4 { ... } 写一堆分组,
改一个就要改一片。CSS 给了我们两个「分组 + 简化」的好工具:
:is() 与 :where()。
它们的写法一模一样,唯一的差别是 :where() 优先级为 0(永远最低,方便被覆盖)。
:is(h1, h2, h3):匹配其中任意一个;优先级 = 里面最高的那条(h1 是 1,h2 是 2…)。:where(h1, h2, h3):写法同上,但优先级永远是 0——用基础选择器可轻松覆盖。- 嵌套可写:
:is(.card, .box) :is(h1, h2)等价于「.card 或 .box 里的 h1 或 h2」。
:is() vs :where():左侧传统分组,右侧两行简化写法。在「覆盖块」里写 .over h2 { color: #4E7A52 }——左边 :is() 因优先级较高没被覆盖,右边 :where() 立刻被绿传统分组
标题一
正文一
覆盖区标题
:is() / :where() 简化
标题二
正文二
覆盖区标题
:where() 包——
这样业务页用类名(.x-title)覆盖时永远不需要加 !important。
这正是 Tailwind / 现代设计系统的核心技巧。七、逻辑属性:writing-mode 自适应的 margin/inline-size
传统的 margin-left / margin-right / width / height 都是「物理属性」——
它们写死了「左」与「右」、「宽」与「高」。
逻辑属性改用「inline / block」描述「文字行进方向」与「文字换行方向」,
自动适配横排、竖排、LTR(从左到右)、RTL(从右到左,如阿拉伯文、希伯来文)。
| 物理属性(LTR 视角) | 逻辑属性(方向无关) | 横排时 | 竖排 / RTL 时 |
|---|---|---|---|
| margin-left | margin-inline-start | 左 | 随方向翻转 |
| margin-right | margin-inline-end | 右 | 随方向翻转 |
| margin-top | margin-block-start | 上 | 随方向翻转 |
| margin-bottom | margin-block-end | 下 | 随方向翻转 |
| width | inline-size | 宽 | 竖排时变「高」 |
| height | block-size | 高 | 竖排时变「宽」 |
| padding-left | padding-inline-start | 左 | 随方向翻转 |
| border-top | border-block-start | 上 | 随方向翻转 |
width / margin-left,竖排后宽度方向死锁;下方用逻辑属性 inline-size / margin-inline-start,竖排后自动变成「高度方向」+「上方 margin」,符合竖排视觉物理属性(竖排翻车)
逻辑属性(竖排正确)
八、本章小结
- 容器查询
@container:让组件响应自己的父容器而非视口;container-type: inline-size是最常用的容器声明。 cqi / cqw / cqh:容器相对单位;font-size: clamp(1rem, 5cqi, 2rem)是「流式字号」的标配写法。:has():父选子的反向选择;表单校验、卡片含图变两栏等场景零 JS 实现。@scope:CSS 局部作用域;解决组件 CSS 互相污染,兼容性还在推广中(Chrome 118+ / Safari 17.4+)。subgrid:子网格沿用父网格轨道;嵌套对齐终于不用算算术。:is() / :where():简化分组选择器;:where()优先级为 0,是「主题默认样式」的最佳载体。- 逻辑属性:
inline-size / block-size / margin-inline-start自动适配 writing-mode 与 RTL,国际化场景必备。
container-type: inline-size,内部用 @container 让图片与文字在窄容器下自动竖排;
第二,给表单加 form:has(input:invalid) { border: 2px solid red },删字段值让红框自动出现;
第三,如果卡片网格有 3 列布局,子卡片网格改用 grid-template-columns: subgrid,让「价格行」自动跨列对齐。
三件事做完,你就掌握了 2022+ 现代 CSS 布局的核心套路。