CSS 容器查询(Container Queries)完全指南:2026 响应式布局新标准 + 实战案例

媒体查询(media query)我们用了十几年,它解决了"网页适配不同屏幕"的问题。但它有一个一直没被解决的硬伤:它只认视口宽度,不认组件自己所处的容器有多宽。

容器查询(Container Queries)就是来补这个洞的。它从 2023 年初开始被所有主流浏览器正式支持,到 2026 年的今天,已经是可以放心用在生产环境的"响应式布局新标准"。这篇会把它从概念到实战、从语法到避坑、从基础用法到组件库级的应用,一次讲全 —— 看完你不仅会用,还能想清楚"什么时候该用它、什么时候不该用"。文章比较长,建议配合代码动手试一遍。

媒体查询差在哪:一个具体的困境

设想一个非常常见的场景:你写了一个文章卡片组件,它需要被复用到很多地方 —— 宽阔的主内容区、狭窄的侧边栏、一个可以拖拽改宽度的面板、一个三栏布局里的中间栏。用媒体查询,你会立刻撞上这样的尴尬:

/* 媒体查询只认视口(viewport)宽度 */
@media (min-width: 768px) {
  .card { display: flex; }
}

/* 问题:同一个 .card 组件,放进一个很窄的侧边栏里,
   只要视口本身够宽,它依然会 display:flex —— 在窄容器里挤成一团。
   媒体查询能问到"屏幕多宽",问不到"我所在的盒子多宽"。 */

问题的根源在于一个参照物的错配:媒体查询的参照物是"整个视口",但一个可复用组件,它根本不知道、也不该关心整个视口有多大 —— 它只该关心"我被塞进了一个多大的盒子里"。

过去为了绕开这个问题,前端想了各种办法,没有一个干净:有的靠父组件传一个 size 之类的 prop 告诉子组件"你现在该用什么形态",这让组件失去了自包含性;有的用 JavaScript 的 ResizeObserver 监听容器尺寸再切 class,这把布局逻辑泄漏到了 JS 里、还有性能和时序问题。这些都是"绕",不是"解"。

容器查询做的,就是从根上把判断的参照物,从"视口"换成"组件的父容器"。组件终于可以问出那个它真正关心的问题了:"我所在的盒子,现在有多宽?"

容器查询的两个核心概念

理解容器查询,只需要抓住两个词:容器查询

  • 容器(container):你主动指定某个元素"作为一个被观察尺寸的容器"。它通常是被响应的那个组件的父级或更上层的祖先,而不是组件本身。
  • 查询(@container):在容器内部的后代元素上,用 @container 规则,根据容器当前的尺寸应用不同的样式。

一句话总结这个模型:祖先声明成容器,后代查询容器的尺寸。这和媒体查询"任何地方都能直接问视口"的全局模型,是完全不同的两种思路 —— 容器查询是有"作用域"的,它只在"容器→后代"这条祖先链上生效。

有一个反直觉、但必须记住的点:查询的对象是容器,被影响的是容器的后代,而不是容器自己。很多人第一次写会犯的错,就是想"让容器自己根据自己的尺寸变样式" —— 这做不到,后面"常见的坑"会专门讲。

container-type:三个值,以及背后的"尺寸限制"

把一个元素声明成容器,靠的是 container-type 属性,它有三个值:

  • inline-size:最常用,绝大多数情况都用它。只监听 inline 方向(在常规横排文档里就是宽度)的尺寸。响应式需求 95% 是按宽度走的,用它。
  • size:同时监听宽度和高度两个方向。用得很少,而且有个硬条件 —— 容器必须有确定的高度,否则会出问题(下面解释为什么)。非必要不用。
  • normal:默认值,即"这不是一个尺寸容器"。也用来在需要时把一个元素的容器身份取消掉。注意:即使是 normal,它仍然可以作为"样式查询"的容器(后面讲)。

这里有一个必须理解的关键机制:尺寸限制(size containment)。当你给一个元素设了 container-type: inline-size,你其实是在告诉浏览器:"在 inline 方向上,这个元素的尺寸不再由它的内容来决定。"

为什么必须这样?想一下:如果容器的宽度由内容决定,而内容的样式又由"容器宽度"的查询决定 —— 这就成了一个先有鸡还是先有蛋的死循环。浏览器为了打破这个循环,规定:一旦你声明了 container-type,这个方向上的尺寸就"封闭"了,不被内容撑开。

这个机制带来一个非常重要的实践原则:不要把 container-type 设在组件自己身上,要设在一个"布局用的包裹层"上。因为组件自己往往需要被内容自然撑开,而布局包裹层的尺寸通常是由它的父布局(Grid/Flex 的格子、固定宽度等)决定的、不依赖内容。这是用容器查询最容易踩、也最隐蔽的坑,记死它。

顺便解释 size 为什么要求"确定的高度":size 同时封闭了宽和高两个方向,如果容器高度本来是被内容撑开的,封闭之后它就坍缩成 0 了。所以除非容器的高度本来就是确定的(比如固定高度、或被 Grid 行高撑满),否则别用 size

@container 语法详解

声明完容器,就可以查询了。基础写法是"声明容器 + 写查询规则"两步:

/* 第 1 步:把某个祖先元素声明为"容器" */
.card-list {
  container-type: inline-size;   /* 监听自身 inline 方向(通常即宽度)的尺寸 */
  container-name: cards;          /* 给容器起个名字,可选 */
}

/* 第 2 步:用 @container 针对这个容器的尺寸写规则 */
@container cards (min-width: 400px) {
  .card {
    display: flex;
    gap: 16px;
  }
}

语法上还有几个值得知道的点:

/* container 简写:一行同时设类型和名字 */
.card-list {
  container: cards / inline-size;   /* 等价于 container-name: cards; container-type: inline-size; */
}

/* @container 的几种写法 */
@container (min-width: 400px) { /* ... */ }            /* 匿名:匹配最近的容器祖先 */
@container cards (min-width: 400px) { /* ... */ }      /* 具名:只匹配名为 cards 的容器 */
@container cards (min-width: 400px) and (max-width: 800px) { /* ... */ }  /* 条件组合 */
@container cards (400px <= width <= 800px) { /* ... */ }  /* 区间语法,更直观 */

几个要点:container 是简写,一行能同时设名字和类型,注意斜杠的位置。container-name 是可选的 —— 不写名字时,@container 会自动匹配"最近的那个容器祖先";写了名字,就能精确指定"我要查的是哪个容器"(在嵌套场景里这很关键)。查询条件支持 min-width / max-width / min-height 等,支持用 and 组合,新版浏览器还支持 (400px <= width <= 800px) 这种更直观的区间语法。

整体逻辑和媒体查询几乎一致 —— 如果你会写 @media,@container 的语法你几乎不用额外学,差别只在"参照物从视口换成了容器"。

容器查询单位:cqw / cqh / cqi / cqb

媒体查询时代我们有 vwvh 这些视口单位,容器查询也配了一整套对应的"容器单位":

容器查询单位一览(参照物是「容器」,不是视口):

  单位    含义                       类比视口单位
  ----------------------------------------------------
  cqw     容器宽度的 1%               vw
  cqh     容器高度的 1%               vh
  cqi     容器 inline 方向尺寸的 1%   横排文档里 ≈ cqw
  cqb     容器 block 方向尺寸的 1%    横排文档里 ≈ cqh
  cqmin   cqi 和 cqb 中较小的那个     vmin
  cqmax   cqi 和 cqb 中较大的那个     vmax

  典型用法:配合 clamp() 做"跟着容器无级缩放"的字号 / 间距
    font-size: clamp(14px, 4cqi, 22px);
    /* 字号 = 容器宽度的 4%,但不小于 14px、不大于 22px */

这套单位真正的价值,在于它能让你做出"跟着容器无级缩放"的组件 —— 注意是"无级",不是"在几个断点上跳变"。

举个例子:用 @container 断点,卡片标题可能是"容器 < 420px 时 16px,>= 420px 时 22px" —— 它在 419px 和 420px 之间会突然跳一下。而用 font-size: clamp(16px, 4cqi, 22px),标题字号会随容器宽度平滑地、连续地变化,只是被限制在 16px~22px 之间。两种手段不冲突,实际项目里经常配合用:大的形态切换(堆叠 vs 并排)用 @container 断点,细微的尺寸过渡(字号、间距、留白)用容器单位 + clamp()

实战一:一个真正自适应的文章卡片

把前面的知识点串起来,做一个文章卡片:容器窄时上下堆叠、隐藏次要信息,容器变宽就图文左右排布、显示更多信息,再宽就加大留白放大标题。整个过程中,这个卡片不需要知道视口多大,也不需要父级用任何 prop 告诉它该怎么显示 —— 它完全自己管自己。

<!-- HTML:一个文章卡片 -->
<div class="card-list">
  <article class="card">
    <img class="card-thumb" src="cover.jpg" alt="">
    <div class="card-body">
      <h3 class="card-title">文章标题</h3>
      <p class="card-excerpt">一段摘要文字……</p>
      <div class="card-meta">2026-05-14 · 阅读 3 分钟</div>
    </div>
  </article>
</div>
/* CSS:卡片完全根据"自己占多宽"来决定布局,不关心视口 */
.card-list { container-type: inline-size; }

/* —— 窄容器(默认):上下堆叠 —— */
.card { display: block; background: #fff; border-radius: 12px; overflow: hidden; }
.card-thumb { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.card-body  { padding: 14px; }
.card-title { font-size: clamp(15px, 4.5cqi, 22px); margin: 0 0 6px; }
.card-meta  { display: none; }   /* 太窄时,次要信息先藏起来 */

/* —— 容器变宽:图文左右排布,次要信息显示出来 —— */
@container (min-width: 420px) {
  .card { display: flex; gap: 16px; }
  .card-thumb { width: 42%; aspect-ratio: 4 / 3; }
  .card-body  { flex: 1; padding: 16px 16px 16px 0; }
  .card-meta  { display: block; color: #888; font-size: 13px; }
}

/* —— 容器更宽:加大留白、放大标题 —— */
@container (min-width: 700px) {
  .card-thumb { width: 36%; }
  .card-body  { padding-block: 22px; }
  .card-title { font-size: 26px; }
  .card-excerpt { font-size: 16px; line-height: 1.7; }
}

注意这个实现里几个有意思的细节:窄容器是"默认样式",容器查询只负责"变宽时增强"(这个思路对兼容性也友好,后面会讲);次要信息(.card-meta)在窄态下直接 display: none —— 容器查询不只是"调样式",还能根据空间"增删内容的可见性";缩略图的宽度和比例都在变 —— 窄态 16:9 通栏,宽态变成 4:3 的侧图。

现在把这个 .card-list 丢进主内容区、丢进侧边栏、丢进一个可拖拽改宽度的面板 —— 它都会自己变成当前宽度下最合适的样子。这就是容器查询最大的价值:组件真正变得"自包含"了,适配逻辑写在组件自己身上,而不是散落在每个使用它的页面里。

实战二:一个仪表盘 widget

再看一个更能体现容器查询威力的场景 —— 仪表盘(dashboard)。仪表盘的特点是:同一个数据卡片(widget),用户可能把它拖成 1 格宽、2 格宽、4 格宽,甚至占满整行。用媒体查询,这是无解的,因为视口没变、变的是 widget 自己占的格数。

用容器查询,思路非常清晰:把每个 widget 的格子设成容器,widget 内部根据格子宽度决定展示形态 ——

  • 1 格宽(很窄):只显示一个核心数字 + 标题。比如"今日订单 1,284"。
  • 2 格宽(中等):核心数字 + 标题 + 一行同比环比的小字。
  • 4 格宽(较宽):核心数字 + 标题 + 同环比 + 一个迷你趋势图。
  • 占满整行(很宽):左边是数字区,右边铺开一个完整的折线图。

实现上,就是给 widget 的容器写几个 @container 断点,每个断点里控制趋势图、小字这些元素的 display 和布局。同一个 widget 组件,不写任何 JS,不传任何 prop,就能在四种宽度下呈现四种合理的形态。这种"一个组件适配所有尺寸槽位"的能力,是仪表盘、低代码搭建、可视化拖拽这类产品里,容器查询不可替代的原因。

进阶:样式查询(Style Queries)

容器查询不止能查"尺寸",还能查容器上的"样式" —— 目前主要是查 CSS 自定义属性(CSS 变量)的值。这叫样式查询:

/* 进阶:样式查询 —— 不查尺寸,查容器上的 CSS 变量值 */
.widget-wrap {
  container-name: widget;
}

/* 父级把 --density 设成 compact,容器内的组件就切到紧凑形态 */
@container widget style(--density: compact) {
  .widget-item { padding: 4px 8px; font-size: 13px; }
}
@container widget style(--density: comfortable) {
  .widget-item { padding: 12px 16px; font-size: 15px; }
}

这给了组件一种全新的"按上下文变形"的能力:父级只要改一个 CSS 变量,容器内的所有组件就能跟着切换形态 —— 不用传 prop、不用加 class、不用写 JS。比如做一个"紧凑模式 / 舒适模式"的全局切换,或者一个区域内的"主题变体",样式查询会非常优雅。

需要注意:样式查询的浏览器支持比尺寸查询晚一些、也还在演进(尤其是"查询任意属性"还没普及,目前稳的是查自定义属性)。用之前查一下 caniuse,非核心功能可以先用着、配合渐进增强;核心功能则建议再等等,或者用尺寸查询 + 一点 JS 兜底。

嵌套容器与"就近原则"

容器是可以嵌套的:一个容器里可以再有容器。比如外层是"页面主区"容器,里面有个"卡片列表"容器,卡片列表里又有"卡片"容器。

这时候,匿名的 @container 查询遵循就近原则 —— 它匹配的是离查询元素最近的那个容器祖先。如果你需要"跨过最近的容器、去查更外层的某个容器",那就必须给容器命名,并在查询时写明名字。

实战建议:在任何稍微复杂一点的布局里,都给关键容器起名字。匿名容器在简单场景下省事,但一旦嵌套层级多了,"就近原则到底匹配到了哪个"会变得难以推理,调试起来很费时间。给容器起名(container-name: page / card-list / card),查询时写明(@container card (...)),代码的可读性和可维护性会好很多。这是个能省掉大量调试时间的好习惯。

容器查询 vs JS 的 ResizeObserver 方案

在容器查询普及之前,要实现"组件根据自身容器尺寸变化",前端的主流做法是用 JavaScript 的 ResizeObserver:监听容器尺寸变化,在回调里给元素加减 class。现在还有不少老项目在这么做。那么容器查询相比 JS 方案,好在哪?

  • 没有时序问题:JS 方案里,组件先以"默认样式"渲染一帧,等 ResizeObserver 回调跑完才切到正确样式 —— 这中间可能有一次肉眼可见的"闪烁/跳动"。容器查询是浏览器原生的、在渲染流程里同步处理的,不存在这一帧的错位。
  • 性能更好:JS 方案每次尺寸变化都要触发 JS 回调、读写 DOM、可能引起额外的重排。容器查询由浏览器内部优化,开销小得多。
  • 关注点不泄漏:JS 方案把"布局逻辑"泄漏到了 JS 里,样式被拆成了"CSS 一部分 + JS 一部分",维护时要两头看。容器查询让布局逻辑回归 CSS,该在哪儿就在哪儿。
  • 不依赖框架、不依赖运行时:纯 CSS,SSR 友好,组件卸载也不用记得清理监听器。

结论很明确:能用容器查询的场景,就不要再用 ResizeObserver 去模拟了。ResizeObserver 仍然有它的用武之地(比如你需要拿到精确的像素值去做 canvas 绘制、虚拟列表计算),但"根据容器尺寸切换样式"这件事,已经是 CSS 的活了。

容器查询 vs 媒体查询:对比与配合

对比项 媒体查询 @media 容器查询 @container
参照物 视口 / 设备 指定的祖先容器
作用范围 全局,任何元素都能直接用 有作用域,只在"容器→后代"链上生效
适合场景 整页大布局、导航折叠、按设备/朝向区分 可复用组件的自适应
组件可移植性 差 —— 组件换个位置就可能要改 好 —— 组件自带适配逻辑,丢哪都对
专用单位 vw / vh / vmin / vmax cqw / cqh / cqi / cqb / cqmin / cqmax
能否查样式 不能 能(样式查询)

必须强调:结论不是"用容器查询取代媒体查询",而是两者分工、配合使用

页面级的骨架布局 —— 比如"窄屏时侧边栏收起来""整体从三栏变单栏""根据横竖屏调整" —— 这些的参照物本来就该是视口,继续用媒体查询,天经地义。组件级的自适应 —— 一个卡片、一个 widget、一个表单项,在不同宽度的容器里怎么排 —— 改用容器查询。一个成熟的 2026 年的项目,一定是媒体查询管"页面",容器查询管"组件",两者各司其职

在组件库 / 设计系统里的应用

容器查询对"做组件库"这件事的影响,是结构性的。

在容器查询之前,一个组件库的卡片组件,要么提供一堆 size 变体(<Card size="sm|md|lg" />)让使用者手动选,要么干脆不管自适应、让使用者自己写媒体查询覆盖。前者把"该用什么尺寸"的决策推给了使用者(而使用者其实也只能猜),后者则根本没解决问题。

有了容器查询,组件库可以提供"真正自适应"的组件:使用者只管把组件放进任意宽度的容器里,组件自己会呈现合适的形态,不需要选 size、不需要写覆盖样式。这大大降低了组件库的使用心智负担。

实践上有几个要点:第一,组件库要把"声明容器"这件事的位置约定清楚 —— 通常是让使用者在外层包裹元素上声明,或者组件自己输出一个包裹层并在上面声明(注意前面说的"别设在组件本体上")。第二,容器查询的断点应该是组件的实现细节,对使用者透明 —— 使用者不需要知道卡片在 420px 会变形,他只需要知道"放哪都好看"。第三,配合 CSS 变量做主题,样式查询能让组件库的"变体系统"更优雅。

浏览器兼容性与渐进增强

尺寸容器查询(container-type / @container / 容器单位)从 2023 年 2 月起,Chrome、Edge、Safari、Firefox 的正式版就全部支持了。也就是说在 2026 年的今天,主流环境可以直接用,不需要任何 polyfill

如果你的项目还要照顾很老的浏览器,用 @supports 做渐进增强即可:

/* 渐进增强:不支持容器查询的老浏览器,拿到的是"窄容器默认样式" */
@supports (container-type: inline-size) {
  .card-list { container-type: inline-size; }
  @container (min-width: 420px) {
    .card { display: flex; }
  }
}
/* 关键:把"窄"写成默认样式,容器查询只负责"变宽时增强"。
   这样不支持的浏览器拿到一个朴素但完全可用的版本,布局不会坏。 */

这里的关键设计思路是:把"窄容器/移动优先"的样式写成默认样式,容器查询只负责"空间变大时的增强"。这样,不支持容器查询的老浏览器,拿到的是一个朴素但完全可用的版本 —— 布局不会坏,只是少了大屏下的优化。这正是"渐进增强"的精髓:基础体验人人都有,高级体验能力允许时再加。

性能:容器查询会拖慢页面吗

一个常见的顾虑:声明了一堆容器、写了一堆 @container,会不会有性能问题?

正常使用下,不会。浏览器对容器查询做了专门的优化:容器尺寸没变时,相关的 @container 规则不会被重新评估;尺寸变化时,也只重新计算受影响的那部分。它的开销,远小于"用 JS 的 ResizeObserver 去模拟同样效果"。

但有两点要注意,属于"别作"的范畴:第一,别滥用 size 类型 —— 它要同时跟踪两个方向 + 强制布局限制,比 inline-size 重,非必要不用。第二,别把页面上每一个元素都设成容器 —— 容器是有成本的,只在"确实需要内部后代响应它尺寸"的那些布局节点上声明。把容器声明在合理的少数布局节点上,而不是无脑铺满,性能就完全不是问题。

六个常见的坑

坑一:把 container-type 设在组件本体上。前面详细讲过 —— 设了之后元素在该方向上的尺寸不再被内容撑开,组件会坍缩或表现异常。正确做法:设在外层的布局包裹元素上。这是头号坑。

坑二:想让"容器自己"根据自己尺寸变样式。@container 影响的是容器的后代,不是容器本身。如果你想让某个元素根据自身尺寸变 —— 要么在它外面再包一层当容器,要么改成查询更外层的具名容器。

坑三:用了 size 却没给容器确定高度。容器会在 block 方向坍缩。除非你真的要按高度响应、且高度是确定的,否则一律用 inline-size

坑四:嵌套容器时,匿名查询匹配错了对象。"就近原则"在多层嵌套时很难一眼推理对。结构一复杂就给容器命名,别赌。

坑五:忘了容器查询有"作用域"。容器查询不像媒体查询那样全局生效 —— 一个元素必须真的是某个"已声明的容器"的后代,@container 才对它有效。如果你的 @container 规则不生效,先检查:这个元素的祖先链上,到底有没有一个设了 container-type 的元素。

坑六:把所有断点逻辑都从媒体查询无脑搬到容器查询。页面级的骨架布局该用媒体查询的,别硬改成容器查询 —— 那样反而绕。分清"页面级"和"组件级",用对工具。

FAQ

容器查询能查兄弟元素或者非祖先元素吗?不能。@container 只能查询"祖先链上被声明为容器"的元素,查不了兄弟、查不了非祖先。这是设计如此 —— 它的模型就是"容器→后代"的单向作用。

它和 CSS Grid / Flexbox 冲突吗?完全不冲突,它们是不同维度的东西。容器查询负责"什么尺寸用什么样式",Grid/Flexbox 负责"在这套样式里,元素具体怎么排"。实战中天天一起用 —— 比如实战二的仪表盘,外层用 Grid 排格子,每个格子是容器,widget 内部根据容器尺寸再用 Flex 排。

容器查询里能用 :hover 之类的伪类吗?能。@container 只是给规则加了一个"容器尺寸"的条件,规则里的选择器该怎么写还怎么写,伪类、组合器都正常用。

SSR / 静态生成的页面能用吗?能,而且容器查询对 SSR 特别友好 —— 它是纯 CSS,服务端渲染出的 HTML + CSS 到了客户端直接就是对的,不像 JS 方案那样需要"水合"之后才生效、中间有一帧错位。

能根据容器尺寸切换的,只有样式吗?能不能切换内容?严格说,容器查询切换的是样式。但通过 display: none / block,你可以控制元素的"显示与否" —— 这在效果上就接近"根据空间增删内容"了(实战一里的 .card-meta 就是这么做的)。真正的"切换 DOM 结构"还是得靠 JS,但很多场景下,"控制可见性"已经够用了。

写在最后

容器查询不是一个"锦上添花"的新特性,它补的是响应式设计里一块缺了十几年的拼图。媒体查询让页面能响应屏幕,容器查询让组件能响应自己的处境 —— 在"组件化"已经是前端绝对标配的今天,后者的意义只会越来越大。

把这篇的要点收一下:祖先声明成容器、后代查询容器尺寸;container-type 几乎只用 inline-size、且要设在布局包裹层而非组件本体上;断点用 @container、无级缩放用容器单位 + clamp();嵌套就给容器命名;它和媒体查询是分工不是替代 —— 媒体查询管页面、容器查询管组件。

上手成本其实很低。今天就可以在你的项目里,挑一个被复用得最别扭、到处要写覆盖样式的组件,用容器查询重写一遍 —— 你会立刻体会到那种"组件自己管自己、丢哪都对"的舒服。一旦体会过,你就回不去了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

AI 编程怎么才能不翻车?2026 资深开发者总结的 8 条实战经验与避坑指南

2026-5-14 16:04:42

技术教程

JavaScript 防抖与节流彻底搞懂:原理、区别、手写实现 + 实战避坑指南

2026-5-14 16:04:43

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索