Stop Treating CSS Container Queries Like Traditional Media Queries

TL;DR · AI 摘要
CSS容器查询与媒体查询本质不同,正确使用能提升组件适应性,但当前仅41.4%开发者实际应用。
核心要点
- 容器查询关注组件容器尺寸,媒体查询关注视口尺寸
- State of CSS调查显示86%开发者知晓但仅41.4%使用容器查询
- 错误使用容器查询会导致组件响应性失效
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- CSS容器查询解析
- 与媒体查询的区别
- 响应对象不同(容器vs视口)
- 作用范围不同(局部vs全局)
- 使用场景
- 可复用组件自适应
- 嵌套布局响应
- 常见误区
- 当作媒体查询使用
- 忽略容器层级影响
金句 / Highlights
值得收藏与分享的关键句。
94%浏览器支持但仅41.4%开发者实际使用容器查询
容器查询关注组件容器尺寸,媒体查询关注视口尺寸
错误使用容器查询会导致组件响应性失效
停止将CSS容器查询当作传统媒体查询来使用 —— Smashing Magazine
- 2026年9月16日
- 0条评论
停止将CSS容器查询当作传统媒体查询来使用
- 12分钟阅读时间
- 在Twitter上分享,在LinkedIn上分享
#### 关于作者
我编写代码。我撰写文章。表情包规则。好吧,认真说,我是一名Web和移动开发人员,经常写作。在空闲时间,我喜欢构建一些产品……更多关于Victor的信息 ↬
#### 电子邮件通讯
每周前端与用户体验技巧。182,000+人信任。
- 与Rémi Parmentier一起构建现代HTML电子邮件
- 庆祝1000万开发者
- 实时UX培训 —— 与Vitaly Friedman一起学习智能界面设计模式
- Figma MCP与Christine Vallaure
- 与Stéphanie Walter一起学习设计师的无障碍设计
尽管浏览器支持广泛,容器查询的使用率却出人意料地低,且经常被误解。让我们看看它们与媒体查询有何不同,何时应使用它们,以及容器查询如何帮助可重用组件自然地响应其出现的上下文。
坦白说,当CSS容器查询首次发布时,我错过了这个消息。当我最终听说它时,我的第一反应是:“既然已经有媒体查询了,我为什么还需要这个?”
现在我知道了,我并不为这种反应感到自豪,但知道我不是唯一有这种想法的人,这让我感到一丝安慰。事实上,我们中有很多人都是如此。
令我感到困惑的是,容器查询并不是一个新功能,目前浏览器支持率约为94%。然而,真正使用它的人却非常少。根据CSS现状调查,86%的开发者了解容器查询,但只有41.4%的人实际使用它们。调查可能存在偏差,无法完全代表整个领域,但这是目前我们能获得的最佳指标。
Kevin Powell在2026年SmashingConf阿姆斯特丹会议上也谈到了这一点:容器查询的采用情况非常糟糕。这对我来说非常奇怪,因为多年来,组件能够根据其外部容器的大小进行调整一直是许多CSS愿望清单的首要事项。
我并不特别关注有多少人使用容器查询,而是更关注他们是如何使用的。我无法涵盖所有人,但从我所看到的情况来看——包括我自己的早期尝试——我们中有很多人使用方式是错误的。
关键问题是,错误的使用方式源于我最初学习它们时的相同印象:它们在第一眼看起来与媒体查询完全一样。由于它们看起来相似,人们很容易假设它们的目的和工作方式类似。
它们并不一样。
注意:我需要在本文开头明确说明,我关注的是使用容器尺寸查询,即一种响应式设计技术,用于响应特定容器的大小。还有容器样式查询,它们响应容器的计算样式(在本文撰写时仍处于实验阶段)。你可以在Smashing Magazine上Juan Diego的文章中了解这些内容,他探讨了它们的潜在使用场景。
@media (min-width: 1024px) {
.card {
display: flex;
}
}和大多数开发者一样,我一直在向浏览器提问:"当前屏幕的宽度是多少?" 就是这样。
媒体查询优雅地回答了这个问题,但当这个 .card 组件被放置在一个 1920px 桌面屏幕的 300px 宽的网格单元格中时会发生什么呢?
媒体查询并不关心这些,它只是完成自己的工作。视口仍然是 1920px,所以 min-width: 1024px 依然会触发,匹配的查询样式会被应用,即使卡片组件只有 300px 的可用空间。最终,卡片中的所有内容都会发生形变、溢出或拥挤。
"媒体查询是愚蠢的。不是说概念层面的愚蠢,而是它们对很多东西都一无所知。事实上,大多数人误以为它们知道得比实际更多。" —— Kevin Powell
我们通常会认为响应式设计纯粹是一个更新完整页面布局的系统,比如从大屏幕的双列布局切换到小屏幕的单列布局。
容器查询关注内部
容器查询比这更聪明。它们使响应式布局更依赖组件内部发生的情况,而不是依赖无法洞察组件内容的外部上下文。更像是在问:"我现在在这个特定位置有多少可用空间?"
这是我们在上一节看到的相同卡片代码示例,但这次使用了容器查询:
.card-wrapper {
container-name: card;
container-type: inline-size;
}
@container card (min-width: 450px) {
.card {
display: flex;
flex-direction: row;
}
}这改变了所有规则。卡片不再受视口影响;它唯一关心的是其父级包装器是否至少有 450px 的内联(即从左到右书写模式中的水平)空间。如果条件成立,组件会采用水平布局;否则,会使用默认的块级显示。
查看 Vayo 的 [Viewport vs Container [forked]](https://codepen.io/smashingmag/pen/01a0a73d-6f39-709f-8dae-037f6b588852) 示例。
查看 Vayo 的 Viewport vs Container [forked] 示例。
"宏观"布局 vs. "微观"布局
区分 @media 和 @container 查询的一个非常有趣的方式是布局类型。
媒体查询用于"宏观"布局;它们关注外部因素。比如页面结构、横跨窗口的页眉、页脚、主网格布局、系统偏好(prefers-color-scheme)、设备能力(触摸屏)等。这些都属于对整个页面结构有重大影响的因素。
我认为容器查询更适合"微观"布局,即存在于"宏观"布局中的大多数元素。当我们需要任何内容根据分配的空间进行响应式调整时,就会使用它们。让人联想到的组件包括卡片、小部件、表单、导航等。
换句话说,就是"页面布局"与"组件布局"之间的区别。
一个元素不应该仅仅因为宽度超过像 768px 这样的抽象阈值就神奇地变成"平板尺寸"。相反,它应该在有足够的空间时切换布局,无论这种空间是在移动设备视口还是桌面侧边栏中出现。
如果你仍然不相信,你知道现代网络上有超过 2300 种独特的视口尺寸吗?你认为有可能为所有这些尺寸都做出适配吗?
我并不是在否定媒体查询。问题在于,在这个响应式和组件复用的时代,布局逻辑更应贴近容器而非视口。当我们以容器和组件为思考单位时,实际上是在依赖内容来决定布局,而不是视口。
这正是它应有的样子。
示例:组件内部的流体排版
响应式排版是许多人都依赖媒体查询实现的一个典型例子。媒体查询自带的 CSS 单位(如 vw、vh 等)与视口尺寸相关,这让媒体查询在根据用户屏幕尺寸调整字体大小时显得非常合适。
.card-title {
font-size: clamp(100%, 1rem + 2vw, 24px);
}但当这个组件被移动到不同的上下文中,例如侧边栏时,视口就变得毫无意义了。此时,由于我们将响应性绑定到了错误的参考点,排版可能会变得过大或过小。
容器查询自带了专属的单位(如 cqi、cqw、cqb 等),我们可以结合这些单位与 CSS 的 clamp() 函数,实现更进一步的响应式组件,让排版根据组件自身进行缩放,而不是依赖视口:
.card-title {
font-size: clamp(1rem, .5rem + 3cqi, 2rem);
}查看 Vayo 的 [Fluid Typography [forked]](https://codepen.io/smashingmag/pen/01a0a73f-1a44-750c-8b40-9b0ab774d981) 示例。
查看 Vayo 的 Fluid Typography [forked] 示例。
通过这种方式,整个代码完全自包含于该元素的特定容器中。
示例:Flexbox 包裹检测
有趣的是,容器查询在某种程度上可以检测组件内部布局的状态。虽然这并非完全可靠,但这是媒体查询无法做到的,因为媒体查询只能观察外部浏览器窗口,对内部布局事件(例如 flex 项目换行)完全无能为力。让我们深入探讨这一点。
Flexbox 在内容包裹(flex-wrap: wrap)方面表现出色,当 flex 容器空间不足时,可以让 flex 项目自动换行到新行。但 CSS 本身无法检测到这种换行的发生。目前没有类似 :wrapped 伪类或 @media (flex-wrapped: true) 这样的媒体查询来实现这一功能。
媒体查询只能观察浏览器窗口,因为它们无法看到内部变化。实际上,flex 换行本质上就是项目自身的宽度状态变化。
例如,如果你有一个用 flexbox 布局的水平菜单,并希望在项目换行时才重新样式化它们,你将不得不使用 JavaScript 和 ResizeObserver 来获取相关信息。但当我们把容器查询嵌套在 flex 项目内部时,可以借助 Kevin Powell 的技巧,无需 JavaScript 即可实现这一目标。核心思路是:当查询容器的内联尺寸时,允许 flex 项目使用 flex-grow: 1。
(大图预览)
其逻辑如下:
- 当空间充足时,两个项目(
.flex-item)并排显示,每个项目宽度正好是父容器宽度的一半。 - 当空间有限时,第二个项目换行到下一行。
- 由于每个项目都启用了
flex-grow,换行后的项目会拉伸以填充父容器的大部分宽度。 - 如果该项目本身是一个容器,它会检测到宽度的突然扩展并触发响应。
/* 弹性容器父元素 */
.flex-layout {
display: flex;
flex-wrap: wrap;
}
/* 将弹性项目注册为容器 */
.flex-item {
container-type: inline-size;
flex: 1 1 390px; /* 扩展以填充空间,在390px时换行 */
}
/* 默认卡片样式(窄版/并排显示) */
.card {
display: flex;
flex-direction: column;
background: #f4f4f4;
}
/* 当有足够的空间容纳完整行时 */
@container (min-width: 600px) {
.card {
flex-direction: row;
align-items: center;
background: #e2f0d9;
}
}这可以正常工作。当父容器尺寸缩小且卡片换行到两行时,卡片项会扩展,容器查询会触发并应用必要的样式。
查看 Vayo 的 CodePen 示例:使用容器查询进行弹性换行检测(已分叉)
查看 Vayo 的示例:使用容器查询进行弹性换行检测(已分叉)
容器查询确实存在副作用
与所有技术一样,容器查询也存在一些需要注意的副作用或限制。
1. 容器需要额外的包装元素
为了按预期工作,容器查询需要类似父-子关系的结构。假设我们有以下结构:
<div class="card">
<div class="card-content">...</div>
</div>我们无法直接查询 .card 组件来调整 .card-content 的样式,例如这样:
/* 不起作用 */
.card {
container-name: card;
container-type: inline-size;
}
@container card (min-width: 400px) {
.card {
display: flex;
}
}这不起作用,因为容器无法查询自身。在上一个示例中,我们正在查询一个卡片容器,然后试图根据其尺寸调整该容器的 display 属性。这会导致无限循环。
相反,我们需要添加一个额外的包装元素,使 .card 成为容器的后代:
<div class="cards">
<div class="card">
<div class="card-content">...</div>
</div>
</div>从这里开始,我们可以查询 .cards 容器并相应地调整 .card 的布局:
.cards {
container-name: cards;
container-type: inline-size;
}
@container cards (min-width: 400px) {
.card {
display: flex;
}
}在媒体查询中,这不会产生影响,因为 @media 不关心你在块中样式化的元素;它只关注视口,而视口始终可用。因此你可以随意在任何元素上添加条件并完成工作。
2. 查询容器尺寸可能导致布局崩溃
当查询容器的块尺寸(即垂直尺寸)而不是 inline-size 时会发生这种情况:
/* 即使内部有内容也会折叠为0px */
.hero-banner {
container-type: size;
}为什么会这样?因为浏览器在计算容器尺寸时不会考虑其子元素。如果我们没有为 .hero-banner 显式指定高度(或 min-height 或 aspect-ratio),浏览器会将其高度设置为 0px。
因此,通常建议通过 inline-size 查询容器,除非你确实需要查询容器的块尺寸。
媒体查询不受此影响,因为它们处理高度的方式与 @media (min-height: ...) 一样,即询问视口并继续处理。
3. 查询无法使用自定义属性
另一个容器查询限制:我们无法根据自定义属性值进行查询:
:root {
--breakpoint-lg: 1600px;
}/* 无法正常工作 */ @container (min-width: var(--breakpoint-lg)) { /* ... */ }
这是因为自定义属性依赖于沿DOM树传递的值。存在一种可能性:依赖自定义属性的容器查询可能会改变该自定义属性本身。这会迅速变得复杂:
:root { --breakpoint-lg: 1600px; }
/* 无法正常工作 */ @container cards (min-width: var(--breakpoint-lg)) { .card { --breakpoint-lg: 1000px; } }
## 何时使用媒体查询和容器查询
我认为没有任何项目应该完全用@container替代@media。媒体查询在响应式布局中仍然发挥着重要作用。关键在于理解关注点分离的原则。
当组件在多个布局场景中使用时,我倾向于使用容器查询。例如,.card元素可能出现在全宽网格或较窄的侧边栏中。如果是这种情况,我们希望组件内容本身决定何时调整样式,而不是依赖查看整个视口的媒体查询。
相反,当组件仅存在于页面层级时,我会选择使用媒体查询。比如始终位于页面顶部的主要导航栏。它直接受视口尺寸影响,这意味着视口是导航栏需要调整时的可靠参考。同样,这涉及"宏观"布局与"微观"布局的区别。
这是我判断使用哪种查询类型的示意图:
## 结论
归根结底,容器查询与媒体查询看起来非常相似的核心原因仅仅是熟悉度。它们并非"全新",但相比媒体查询,人们对容器查询的理解和采用程度要低得多。但媒体查询本身也有诸多限制,否则我们就不需要容器查询来填补这些空白。
我们拥有的是一个更有效的功能:可以检测特定组件上下文何时发生变化,并根据其内容调整样式,这正是当该组件可以存在于多种上下文时应有的表现方式。
(gg, yk)
Smashing Newsletter 前端与UX技巧,每周发送到你的邮箱。只包含真正实用的内容。
前端与UX工作坊,在线参与。包含实用收获、现场课程、视频录像和友好的问答环节。
TypeScript 50课 详解TypeScript,包含代码讲解和示例。以及其他印刷书籍。