Container
Queries
@media 的三宗罪
Media Query 为页面而生,却被迫用在组件上。结果是:脆弱、不可复用、反直觉。
← 两个 Card 布局完全相同,即使 sidebar 只有 300px
← Sidebar Card 紧凑,Main Card 宽松,各自适应自己的容器
核心原理:两步走
Container Queries 的核心只有两个概念:建立容器 + 查询容器。
建立容器上下文
在父元素上设置 container-type,告诉浏览器:“这个元素的尺寸将成为子元素响应式查询的基准。”
inline-size查询内联轴(通常是宽度)推荐size查询两个轴(宽度+高度)少见normal不建立容器上下文默认查询容器尺寸
在子元素(或其后代)上使用 @container 规则,基于容器宽度来应用样式。
/* ❶ 定义容器上下文 (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 为容器命名,确保查询到正确的容器。这避免了 “查到了错误的祖先” 的问题。
/* 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,但基于容器
cqiContainer Query Inline
容器内联尺寸的 1%
cqbContainer Query Block
容器块级尺寸的 1%
cqminContainer Query Min
cqi 和 cqb 中较小者的 1%
cqmaxContainer Query Max
cqi 和 cqb 中较大者的 1%
/* 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);
}简写属性
/* 完整写法 */
.element {
container-type: inline-size;
container-name: sidebar;
}
/* 等价简写 */
.element {
container: inline-size / sidebar;
/* ↑ type ↑ name */
}动手试试 👇
拖动滑块改变容器宽度,观察下方卡片如何自动切换布局——
全程零 JavaScript 断点逻辑,纯 CSS Container Queries 驱动。
Midnight Purple Headphones
Premium wireless headphones with 40h battery life, ANC, and spatial audio support.
上方卡片使用了两个 @container 断点:350px(图文横排)和 550px(显示元信息标签 + 更大图片)。 这些断点由容器宽度决定,与你的屏幕大小无关。
实战:自适应产品卡片
一个真正 “到处能用” 的产品卡片组件。放到 Grid 列里、Sidebar 里、Modal 里,它自动适配。
// 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.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 /> 组件,在三种不同容器宽度下的行为:
// ✅ 完全自适应的组件 —— 放到任何容器中都能正常工作
// Sidebar (300px) → 紧凑单列
// Grid column (400px) → 图文横排
// Full-width (800px) → 宽松布局 + 元信息
<ResponsiveCard product={headphones} />纵向堆叠
图片满宽,隐藏元信息
图文横排
小正方形图片,显示按钮
宽松布局
大图片 + 元信息标签
进阶:Style Queries(实验性 🧪)
Style Queries(@container style()) 截至 2024 年底仅在 Chrome 111+ 中可用(flag: Experimental Web Platform Features)。 Firefox 和 Safari 尚未支持。请勿在生产环境中使用。
/* 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;
}
}工程考量
每个新技术都有坑。这些是 Container Queries 在生产环境中的关键考量。
浏览器兼容性
| Browser | Version | Date | Status |
|---|---|---|---|
| Chrome | 105+ | Aug 2022 | ✅ Full |
| Edge | 105+ | Sep 2022 | ✅ Full |
| Firefox | 110+ | Feb 2023 | ✅ Full |
| Safari | 16+ | Sep 2022 | ✅ Full |
| Samsung Internet | 20+ | Mar 2023 | ✅ Full |
| Opera | 91+ | Oct 2022 | ✅ Full |
数据来源:caniuse.com · 截至 2024 年底,全球浏览器覆盖率已达 93%+
Container Queries 建立在 CSS Containment 之上。 设置 container-type: inline-size 等同于隐式设置了 contain: inline-size, 这让浏览器可以:
Layout Isolation
容器内布局变化不会触发外部重排(Reflow)
Paint Containment
浏览器可以独立光栅化容器区域
Skip Off-screen
不可见容器可以跳过子元素的样式计算
常见陷阱 & Anti-Patterns
/* ❌ 不要用 @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: 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 要求元素有显式的宽高。 如果高度是 auto,cqb 单位会等于 0。 99% 的情况用 inline-size 就够了。
每个容器上下文都有渲染开销。嵌套 5-6 层容器上下文会导致布局计算成本翻倍。 实际经验:组件库中 2-3 层足够(Layout → Section → Component)。
推荐的容器层级
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速查手册 📋
container-type
定义容器查询的轴
inline-size仅内联轴(推荐)size两个轴normal不创建上下文container-name
命名容器,精确查询
container-name: sidebar;禁止用 “none” 以外的 CSS 关键字
@container
查询容器条件
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?
- 页面整体骨架布局
- 显示/隐藏整块区域(如 sidebar)
- 全局排版调整(如 base font-size)
- 打印样式(@media print)
- 组件内部布局切换(Card、Nav、Widget)
- 可复用的 UI 组件库
- Grid 项自适应(列数变化时卡片重排)
- 字体大小随容器平滑缩放
核心记忆点
一句话总结
Container Queries 把响应式设计的粒度从 页面 细化到了 组件。 组件从此可以 “write once, adapt anywhere”——这才是组件化的终极形态。