CSS Container Queries 完全指南:比媒体查询更强大的响应式方案

2862 字
14 分钟
CSS Container Queries 完全指南:比媒体查询更强大的响应式方案

前言:媒体查询的局限性#

在过去十多年里,@media 媒体查询一直是响应式设计的基石。我们通过检测视口宽度来决定布局如何变化:

/* 传统媒体查询 —— 基于视口 */
@media (max-width: 768px) {
.card {
flex-direction: column;
}
}

这在页面级布局中运作良好,但当我们进入组件化开发时代,问题暴露无遗:

  1. 组件不知道自己被放在哪里。 一个 .card 组件可能出现在全宽的主内容区,也可能出现在 300px 的侧边栏里。视口宽度相同,但组件的可用空间截然不同。
  2. 样式与容器耦合。 你不得不为「侧边栏中的卡片」和「主区域中的卡片」写不同的选择器,破坏了组件的封装性。
  3. 断点爆炸。 随着布局嵌套层级增加,你需要越来越多的媒体查询组合来覆盖各种场景。

CSS Container Queries 的出现,彻底解决了这个问题——让组件根据自身容器的尺寸来响应,而非视口尺寸

核心概念#

什么是 Container Query?#

Container Query 允许你查询一个祖先容器元素的尺寸,并据此应用样式。这意味着组件可以自己决定:「我的父容器有多宽?我该如何渲染?」

/* 声明一个容器 */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* 查询容器尺寸 */
@container sidebar (max-width: 300px) {
.card {
flex-direction: column;
}
}
@container sidebar (min-width: 301px) {
.card {
flex-direction: row;
}
}

这段代码的含义是:当 .card 所在的 sidebar 容器宽度小于等于 300px 时,卡片纵向排列;否则横向排列。无论视口多大,组件只关心自己的容器。

container-type:容器类型#

要让一个元素成为「可查询的容器」,必须显式声明 container-type。这是因为浏览器需要对容器建立尺寸封闭(size containment),以避免循环依赖。

含义
normal默认值,不是查询容器
size在行轴和块轴都建立尺寸封闭,可查询宽度和高度
inline-size仅在行轴(通常是水平方向)建立尺寸封闭,只可查询宽度
/* 只查询宽度(最常用) */
.wrapper {
container-type: inline-size;
}
/* 查询宽度和高度 */
.wrapper {
container-type: size;
}

为什么 inline-size 更常用? 因为 size 要求在两个轴都建立封闭,这意味着容器的高度不能由内容撑开(intrinsic sizing 被禁用),在很多布局场景中不实用。而 inline-size 只约束宽度方向,高度仍然可以自适应内容,适用于绝大多数场景。

container-name:命名容器#

当页面中有多层嵌套容器时,你需要指定查询哪个容器。container-name 就是用来给容器命名的:

.page {
container-type: inline-size;
container-name: page;
}
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* 精确查询 sidebar 容器 */
@container sidebar (max-width: 250px) {
.nav-link {
font-size: 0.875rem;
}
}
/* 精确查询 page 容器 */
@container page (min-width: 1200px) {
.hero {
padding: 4rem;
}
}

如果不指定名称,@container 会查询最近的祖先容器

/* 查询最近的祖先容器 */
@container (min-width: 400px) {
.card-title {
font-size: 1.5rem;
}
}

简写属性 container#

containercontainer-typecontainer-name 的简写:

/* 简写 */
.sidebar {
container: sidebar / inline-size;
}
/* 等同于 */
.sidebar {
container-name: sidebar;
container-type: inline-size;
}

@container 语法深入#

基本查询语法#

@container 的语法与 @media 非常相似,支持 min-widthmax-widthmin-heightmax-height

@container (min-width: 600px) {
.card {
grid-template-columns: 200px 1fr;
}
}
@container (max-width: 599px) {
.card {
grid-template-columns: 1fr;
}
}

使用新范围语法#

与现代媒体查询一样,Container Queries 也支持范围语法(range syntax),更直观:

/* 传统写法 */
@container (min-width: 400px) and (max-width: 799px) {
.card { /* ... */ }
}
/* 范围语法 —— 更清晰 */
@container (400px <= width <= 799px) {
.card { /* ... */ }
}
/* 单侧范围 */
@container (width >= 800px) {
.card { /* ... */ }
}

组合条件#

支持 andornot 逻辑运算:

/* 同时满足宽度和高度条件(需要 container-type: size) */
@container card-wrapper (width >= 400px) and (height >= 300px) {
.card {
display: grid;
grid-template-rows: 200px 1fr;
}
}
/* 取反 */
@container sidebar not (width >= 300px) {
.widget {
display: none;
}
}

命名容器查询#

@container sidebar (width < 300px) {
.nav-item span {
display: none; /* 隐藏文字,只显示图标 */
}
}
@container main-content (width >= 900px) {
.article {
columns: 2;
column-gap: 2rem;
}
}

容器查询单位#

Container Queries 引入了一套新的容器相对单位,类似于视口单位(vwvh),但基于容器尺寸:

单位含义
cqw容器宽度的 1%
cqh容器高度的 1%
cqi容器行轴尺寸的 1%
cqb容器块轴尺寸的 1%
cqmincqicqb 中较小的值
cqmaxcqicqb 中较大的值
.card-container {
container-type: inline-size;
}
.card-title {
/* 字体大小随容器宽度缩放,但有上下限 */
font-size: clamp(1rem, 4cqi, 2rem);
}
.card-image {
/* 图片高度为容器宽度的 56.25%(16:9 比例) */
height: 56.25cqi;
object-fit: cover;
}

这些单位特别适合做流式排版(fluid typography)等比缩放,而且是基于组件容器而非视口。

实战案例#

案例 1:自适应卡片组件#

这是最经典的用例——一个卡片组件,根据所在容器宽度自动切换布局:

<div class="card-wrapper">
<article class="card">
<img src="cover.jpg" alt="封面" class="card__image" />
<div class="card__body">
<h3 class="card__title">文章标题</h3>
<p class="card__excerpt">这是文章摘要...</p>
<a href="#" class="card__link">阅读更多</a>
</div>
</article>
</div>
.card-wrapper {
container: card / inline-size;
}
/* 默认:小容器,纵向堆叠 */
.card {
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card__image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card__body {
padding: 1rem;
}
.card__title {
font-size: 1.125rem;
margin-bottom: 0.5rem;
}
/* 中等容器:横向排列 */
@container card (width >= 400px) {
.card {
flex-direction: row;
}
.card__image {
width: 40%;
height: auto;
min-height: 180px;
}
.card__body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
}
/* 大容器:增强样式 */
@container card (width >= 700px) {
.card__title {
font-size: 1.75rem;
}
.card__excerpt {
font-size: 1.125rem;
line-height: 1.8;
}
.card__image {
width: 45%;
}
}

现在,无论这个卡片被放在全宽区域、双栏布局的一侧,还是窄窄的侧边栏里,它都能自动适应。完全不需要知道视口有多宽。

案例 2:响应式导航栏#

.nav-container {
container: nav / inline-size;
}
.nav {
display: flex;
align-items: center;
gap: 1rem;
}
.nav-links {
display: flex;
gap: 0.5rem;
}
.nav-label {
display: inline;
}
.nav-hamburger {
display: none;
}
/* 窄容器:隐藏文字标签,只显示图标 */
@container nav (width < 600px) {
.nav-label {
display: none;
}
}
/* 更窄:切换为汉堡菜单 */
@container nav (width < 400px) {
.nav-links {
display: none;
}
.nav-hamburger {
display: block;
}
}

案例 3:仪表盘数据面板#

.dashboard-panel {
container: panel / inline-size;
}
.stats-grid {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
/* 中等面板:两列 */
@container panel (width >= 500px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 大面板:四列 + 图表可见 */
@container panel (width >= 900px) {
.stats-grid {
grid-template-columns: repeat(4, 1fr);
}
.stats-chart {
display: block;
height: 300px;
}
}
/* 小面板:精简模式 */
@container panel (width < 300px) {
.stats-item {
padding: 0.5rem;
}
.stats-item__label {
font-size: 0.75rem;
}
.stats-item__value {
font-size: 1.25rem;
}
.stats-item__trend {
display: none;
}
}

配合组件化开发#

Container Queries 的最大价值在于它天然适配组件化架构。以 Vue 为例:

<template>
<div class="product-card-container">
<article class="product-card">
<img :src="product.image" :alt="product.name" class="product-card__image" />
<div class="product-card__info">
<h3 class="product-card__name">{{ product.name }}</h3>
<p class="product-card__price">¥{{ product.price }}</p>
<p class="product-card__desc">{{ product.description }}</p>
<button class="product-card__btn">加入购物车</button>
</div>
</article>
</div>
</template>
<style scoped>
.product-card-container {
container: product / inline-size;
}
.product-card {
display: flex;
flex-direction: column;
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
transition: box-shadow 0.2s;
}
.product-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.product-card__image {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
.product-card__info {
padding: 1rem;
}
.product-card__name {
font-size: 1rem;
font-weight: 600;
}
.product-card__desc {
display: none;
}
/* 中等容器:显示描述 */
@container product (width >= 280px) {
.product-card__desc {
display: block;
color: #6b7280;
font-size: 0.875rem;
margin-top: 0.5rem;
}
}
/* 大容器:横向布局 */
@container product (width >= 500px) {
.product-card {
flex-direction: row;
}
.product-card__image {
width: 200px;
aspect-ratio: auto;
}
.product-card__info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding: 1.5rem;
}
.product-card__name {
font-size: 1.25rem;
}
.product-card__btn {
align-self: flex-start;
margin-top: 1rem;
}
}
</style>

这个组件在任何地方都能正确工作——放在三列网格里、放在侧边栏里、放在全宽 banner 里。组件自身就是响应式的。

容器查询 vs 媒体查询:何时用哪个?#

场景推荐方案
页面整体布局(如导航栏位置、页脚样式)@media
组件内部布局适配@container
打印样式@media print
暗色模式@media (prefers-color-scheme)
嵌套在不同宽度容器中的卡片@container
组件库 / 设计系统中的通用组件@container

简单原则:页面级用 @media,组件级用 @container

底层原理:Size Containment#

Container Queries 能工作的前提是尺寸封闭(size containment)。当你设置 container-type: inline-size 时,浏览器实际上在该元素上应用了 contain: inline-size layout style

这意味着:

  1. 容器的行轴尺寸不受子元素影响。 浏览器可以先确定容器宽度,再渲染子元素,避免循环依赖。
  2. Layout containment 确保容器内部的布局变化不会影响外部。
  3. Style containment 确保计数器等样式效果不会泄漏到容器外。

这也解释了为什么 container-type: size 比较少用——它在块轴也建立封闭,导致容器高度无法由内容撑开,必须显式设置高度。

/* ⚠️ 使用 size 时要注意高度 */
.fixed-panel {
container-type: size;
height: 400px; /* 必须显式指定高度 */
}
/* ✅ inline-size 更安全 */
.flexible-panel {
container-type: inline-size;
/* 高度自动由内容决定 */
}

浏览器兼容性与渐进增强#

截至 2026 年,所有主流浏览器都已支持 Container Queries:

  • Chrome 105+
  • Firefox 110+
  • Safari 16+
  • Edge 105+

对于需要兼容旧浏览器的项目,可以使用渐进增强策略:

/* 基础样式:所有浏览器都能工作 */
.card {
display: flex;
flex-direction: column;
}
/* 媒体查询作为 fallback */
@media (min-width: 768px) {
.card {
flex-direction: row;
}
}
/* Container Query 覆盖(支持的浏览器) */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (width >= 400px) {
.card {
flex-direction: row;
}
}
}

常见陷阱与最佳实践#

1. 容器不能查询自身#

/* ❌ 错误:不能查询自身的尺寸来改变自身 */
.box {
container-type: inline-size;
}
@container (width >= 300px) {
.box {
padding: 2rem; /* 这不会应用到 .box 自身 */
}
}
/* ✅ 正确:查询的是祖先容器,样式应用到后代 */
.box-wrapper {
container-type: inline-size;
}
@container (width >= 300px) {
.box {
padding: 2rem;
}
}

2. 避免不必要的 size 类型#

/* ❌ 除非你真的需要查询高度,否则不要用 size */
.panel {
container-type: size;
}
/* ✅ 绝大多数场景用 inline-size */
.panel {
container-type: inline-size;
}

3. 合理命名容器#

/* ❌ 不命名,依赖最近祖先,容易在嵌套时出错 */
.a { container-type: inline-size; }
.b { container-type: inline-size; }
@container (width >= 500px) {
.target { /* 查的是哪个? */ }
}
/* ✅ 明确命名 */
.a { container: layout-a / inline-size; }
.b { container: layout-b / inline-size; }
@container layout-a (width >= 500px) {
.target { /* 明确查询 layout-a */ }
}

4. 一个元素可以是多个容器名#

.panel {
container-name: panel sidebar widget;
container-type: inline-size;
}
/* 以下三种查询都能匹配到同一个元素 */
@container panel (width >= 300px) { /* ... */ }
@container sidebar (width >= 300px) { /* ... */ }
@container widget (width >= 300px) { /* ... */ }

总结#

CSS Container Queries 是自 Flexbox/Grid 以来最重要的 CSS 布局特性之一。它从根本上改变了我们编写响应式样式的思维方式:

  • 从视口思维到容器思维:组件不再需要知道页面整体布局。
  • 真正的组件封装:样式响应逻辑内聚在组件内部。
  • 减少样式冗余:不再需要为不同上下文写不同的媒体查询组合。
  • 更好的可维护性:组件迁移到新的布局环境时,无需修改样式。

如果你还没有在项目中使用 Container Queries,现在是时候了。从最简单的卡片组件开始,逐步迁移——你会发现代码变得更干净、更健壮、更易维护。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

CSS Container Queries 完全指南:比媒体查询更强大的响应式方案
https://boke.hackerdream.xyz/posts/css-container-queries/
作者
晴天
发布于
2026-01-04
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
125
分类
17
标签
287
总字数
257,955
运行时长
0
最后活动
0 天前

目录