CSS Containment Level 3

Container
Queries

组件级响应式设计——让组件不再 “看窗外”(视口宽度), 而是 “照镜子”(父容器宽度)来决定自己的布局。 这是从 @media @container 的范式转变。

传统 @media

组件只能看到浏览器视口宽度。 同一个 Card 放在 sidebar 300px 和 main 900px 里,表现完全一样。

新范式 @container

组件看到的是自己的容器宽度。 放在 300px sidebar → 紧凑布局;放在 900px main → 宽松布局。真正的 “write once, adapt anywhere”。

Why We Need It

@media 的三宗罪

Media Query 为页面而生,却被迫用在组件上。结果是:脆弱、不可复用、反直觉。

组件看不见容器

Media Query 只能看到视口。一个 Sidebar 300px 里的 Card 和 Main 900px 里的 Card,因为视口相同,表现一模一样。

100%的组件响应依赖视口

断点靠猜

页面级断点(768px/1024px/1280px)是设计师拍脑袋的产物。一个 Card 可能在 400px 时就需要横排,但 @media 只能在 768px 才能触发。

3-5个全局断点打天下

复用性 = 0

同一个组件放在 Grid 列、Sidebar、Modal 中,布局宽度从 200px 到 800px 都有。用 @media?你得给每种容器写一套覆盖样式。

N × M种媒体查询 × 容器组合
❌ Media Query
viewport: 1200px

← 两个 Card 布局完全相同,即使 sidebar 只有 300px

✅ Container Query
每个容器独立查询

← Sidebar Card 紧凑,Main Card 宽松,各自适应自己的容器

How It Works

核心原理:两步走

Container Queries 的核心只有两个概念:建立容器 + 查询容器

1

建立容器上下文

在父元素上设置 container-type,告诉浏览器:“这个元素的尺寸将成为子元素响应式查询的基准。”

inline-size查询内联轴(通常是宽度)推荐
size查询两个轴(宽度+高度)少见
normal不建立容器上下文默认
2

查询容器尺寸

在子元素(或其后代)上使用 @container 规则,基于容器宽度来应用样式。

两步走示例css
/* ❶ 定义容器上下文 (Containment Context) */
.card-wrapper {
  container-type: inline-size;
  /* 告诉浏览器:这个元素的子元素将查询
     它的「内联尺寸」(通常=宽度) */
}

/* ❷ 查询容器,而非视口! */
@container (min-width: 400px) {
  .card {
    flex-direction: row;  /* ← 容器 ≥ 400px 时横排 */
  }
}

@container (min-width: 700px) {
  .card {
    padding: 2rem;        /* ← 容器 ≥ 700px 时更宽松 */
    gap: 2rem;
  }
}

命名容器:精确控制查询目标

当页面有多个嵌套容器时,可以通过 container-name 为容器命名,确保查询到正确的容器。这避免了 “查到了错误的祖先” 的问题。

Named Containerscss
/* sidebar 和 main 各自成为独立的容器 */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;       /* ← 命名容器 */
}

.main-content {
  container-type: inline-size;
  container-name: main;          /* ← 另一个命名容器 */
}

/* 精确查询某一个容器 */
@container sidebar (min-width: 300px) {
  .nav-item {
    flex-direction: row;         /* ← 仅 sidebar ≥ 300px 生效 */
    gap: 0.75rem;
  }
}

@container main (min-width: 600px) {
  .article-grid {
    grid-template-columns: 1fr 1fr; /* ← 仅 main ≥ 600px 生效 */
  }
}

容器查询单位:像 vw,但基于容器

cqi

Container Query Inline

容器内联尺寸的 1%

cqb

Container Query Block

容器块级尺寸的 1%

cqmin

Container Query Min

cqi 和 cqb 中较小者的 1%

cqmax

Container Query Max

cqi 和 cqb 中较大者的 1%

Container Query Units 实用示例css
/* Container Query Units —— 容器查询单位 */
.hero-title {
  /* cqi = container inline size 的 1%
     让字体随容器平滑缩放 */
  font-size: clamp(1.5rem, 5cqi, 4rem);
}

.card-grid {
  /* 每个子项至少占容器宽度的 28% */
  grid-template-columns:
    repeat(auto-fill, minmax(28cqi, 1fr));
}

.card-avatar {
  /* 头像始终是容器宽度的 12%,但不小 40px */
  width: clamp(40px, 12cqi, 120px);
  height: clamp(40px, 12cqi, 120px);
}

简写属性

container shorthandcss
/* 完整写法 */
.element {
  container-type: inline-size;
  container-name: sidebar;
}

/* 等价简写 */
.element {
  container: inline-size / sidebar;
  /*         ↑ type     ↑ name */
}
Interactive Lab

动手试试 👇

拖动滑块改变容器宽度,观察下方卡片如何自动切换布局——全程零 JavaScript 断点逻辑,纯 CSS Container Queries 驱动。

200px
500px中等 · Medium
750px
🎧

Midnight Purple Headphones

Premium wireless headphones with 40h battery life, ANC, and spatial audio support.

$299★ 4.8Audio

上方卡片使用了两个 @container 断点:350px(图文横排)和 550px(显示元信息标签 + 更大图片)。 这些断点由容器宽度决定,与你的屏幕大小无关。

Active Rules → 中等 · Mediumcss
@container card-demo (min-width: 350px) {
  .cq-card {
    flex-direction: row;      /* ← 切换为横排 */
    align-items: flex-start;
  }
  .card-img {
    width: 96px; min-width: 96px;
    height: 96px; border-radius: 16px;
  }
  .card-meta  { display: none }
  .card-actions { flex-direction: row }
}

注意看:左右两侧是同步的。改变宽度时,卡片的 CSS 自动切换,没有任何 JavaScript 参与断点判断——这才是容器查询的真正力量。

Production Code

实战:自适应产品卡片

一个真正 “到处能用” 的产品卡片组件。放到 Grid 列里、Sidebar 里、Modal 里,它自动适配。

ResponsiveCard.tsxtsx
// ResponsiveCard.tsx —— 真正可复用的自适应组件
interface Product {
  image: string;
  name: string;
  description: string;
  price: number;
  rating: number;
  category: string;
}

export function ResponsiveCard({ product }: { product: Product }) {
  return (
    // ↓ 这一行让此 div 成为容器上下文
    <div style={{ containerType: "inline-size" }}>
      <article className="card">
        <img src={product.image} className="card-img" />
        <div className="card-body">
          <h3 className="card-title">{product.name}</h3>
          <p className="card-desc">{product.description}</p>
          <div className="card-meta">
            <span className="tag">${product.price}</span>
            <span className="tag">{product.rating}</span>
            <span className="tag">{product.category}</span>
          </div>
          <div className="card-actions">
            <button className="btn-primary">Add to Cart</button>
            <button className="btn-ghost">Save</button>
          </div>
        </div>
      </article>
    </div>
  );
}
ResponsiveCard.csscss
/* ResponsiveCard.css —— 无需任何 JS 断点逻辑 */
.card {
  display: flex;
  flex-direction: column;     /* 默认:纵向堆叠 */
  gap: 1rem;
}

/* ── 容器 ≥ 350px → 横向布局 ── */
@container (min-width: 350px) {
  .card {
    flex-direction: row;
    align-items: flex-start;
  }
  .card-img {
    width: 96px;
    height: 96px;
    border-radius: 16px;
  }
}

/* ── 容器 ≥ 550px → 宽松横向 + 元信息 ── */
@container (min-width: 550px) {
  .card       { padding: 1.75rem; gap: 1.5rem }
  .card-img   { width: 160px; height: 160px }
  .card-title { font-size: 1.5rem }
  .card-meta  { display: flex }   /* 默认 hidden */
}
组件组合示例

同一个 <ResponsiveCard /> 组件,在三种不同容器宽度下的行为:

组合使用tsx
// ✅ 完全自适应的组件 —— 放到任何容器中都能正常工作
// Sidebar (300px) → 紧凑单列
// Grid column (400px) → 图文横排
// Full-width (800px) → 宽松布局 + 元信息
<ResponsiveCard product={headphones} />
Sidebar · 280px

纵向堆叠

图片满宽,隐藏元信息

Grid · 400px

图文横排

小正方形图片,显示按钮

Full · 700px

宽松布局

大图片 + 元信息标签

进阶:Style Queries(实验性 🧪)

Style Queries@container style()) 截至 2024 年底仅在 Chrome 111+ 中可用(flag: Experimental Web Platform Features)。 Firefox 和 Safari 尚未支持。请勿在生产环境中使用。

Style Queries (Experimental)css
/* Style Queries (实验性) —— 查询 CSS 自定义属性 */
.theme-wrapper {
  --theme: light;
}
.theme-wrapper.dark {
  --theme: dark;
}

/* 根据父容器的 CSS 变量切换样式 */
@container style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: #f8fafc;
    border-color: #334155;
  }
}

@container style(--theme: high-contrast) {
  .card {
    border-width: 3px;
    font-weight: 700;
  }
}
Engineering Notes

工程考量

每个新技术都有坑。这些是 Container Queries 在生产环境中的关键考量。

浏览器兼容性

BrowserVersionDateStatus
Chrome105+Aug 2022✅ Full
Edge105+Sep 2022✅ Full
Firefox110+Feb 2023✅ Full
Safari16+Sep 2022✅ Full
Samsung Internet20+Mar 2023✅ Full
Opera91+Oct 2022✅ Full

数据来源:caniuse.com · 截至 2024 年底,全球浏览器覆盖率已达 93%+

性能正面影响

Container Queries 建立在 CSS Containment 之上。 设置 container-type: inline-size 等同于隐式设置了 contain: inline-size, 这让浏览器可以:

~60%减少重排范围

Layout Isolation

容器内布局变化不会触发外部重排(Reflow)

~30%减少绘制面积

Paint Containment

浏览器可以独立光栅化容器区域

~40%节省计算开销

Skip Off-screen

不可见容器可以跳过子元素的样式计算

常见陷阱 & Anti-Patterns

Anti-Pattern #1:用 @container 替代 @media 做页面级布局
错误
/* ❌ 不要用 @container 控制页面骨架 */
body {
  container-type: inline-size;
}

@container (min-width: 1024px) {
  .page-layout {
    grid-template-columns: 250px 1fr;
  }
}
正确
/* ✅ 页面骨架用 @media */
@media (min-width: 1024px) {
  .page-layout {
    grid-template-columns: 250px 1fr;
  }
}

/* ✅ 组件用 @container */
@container sidebar (min-width: 280px) {
  .sidebar-nav { flex-direction: row }
}

黄金法则:@media 管页面骨架(viewport),@container 管组件内容(component)。 两者不是替代关系,而是互补关系。

Anti-Pattern #2:container-type: size 没给显式高度
size vs inline-sizecss
/* ❌ Anti-pattern: container-type: size 没给显式高度 */
.grid-item {
  container-type: size;
  /* 高度 auto → cqb 单位 = 0 → 布局崩溃 */
}

/* ✅ Correct: inline-size 或给定显式高度 */
.grid-item {
  container-type: inline-size;  /* 只查宽度,安全 */
  /* 或 */
  container-type: size;
  height: 300px;                /* 显式高度 */
}

记住:container-type: size 要求元素有显式的宽高。 如果高度是 autocqb 单位会等于 0。 99% 的情况用 inline-size 就够了。

Anti-Pattern #3:过度嵌套容器上下文

每个容器上下文都有渲染开销。嵌套 5-6 层容器上下文会导致布局计算成本翻倍。 实际经验:组件库中 2-3 层足够(Layout → Section → Component)。

推荐的容器层级

L1 Page Layout → container-type: inline-size├─ L2 Section/Sidebar → container-type: inline-size│ └─ L3 Component (Card, Nav) → container-type: inline-size└─ L3 Component (Card, Nav) → container-type: inline-size
Cheat Sheet

速查手册 📋

container-type

定义容器查询的轴

inline-size仅内联轴(推荐)
size两个轴
normal不创建上下文

container-name

命名容器,精确查询

container-name: sidebar;

禁止用 “none” 以外的 CSS 关键字

@container

查询容器条件

@container (min-width: 400px) { }
@container name (min-inline-size: 300px) { }
@container name (width < 600px) { }

Container Query Units

容器相对单位,替代 vw/vh

cqi容器 inline 尺寸的 1%
cqb容器 block 尺寸的 1%
cqmincqi/cqb 中较小者的 1%
cqmaxcqi/cqb 中较大者的 1%

Shorthand

一行搞定

container: inline-size / name;

type / name

什么时候用 @media,什么时候用 @container?

@media(视口)
  • 页面整体骨架布局
  • 显示/隐藏整块区域(如 sidebar)
  • 全局排版调整(如 base font-size)
  • 打印样式(@media print)
@container(容器)
  • 组件内部布局切换(Card、Nav、Widget)
  • 可复用的 UI 组件库
  • Grid 项自适应(列数变化时卡片重排)
  • 字体大小随容器平滑缩放

核心记忆点

🪞Container Queries 让组件看容器,而非视口
🧩两步走:container-type → @container (condition)
🎯命名容器 (container-name) 精确查询目标
📏cqi/cqb 单位让字体/尺寸随容器平滑缩放
内置 Containment 优化,性能比 Media Query 更好
🤝与 @media 互补,不是替代

一句话总结
Container Queries 把响应式设计的粒度从 页面 细化到了 组件。 组件从此可以 “write once, adapt anywhere”——这才是组件化的终极形态。