媒体查询(@media)按视口尺寸响应式。但同一个组件可能放在不同宽度的
容器里(侧栏窄 / 主区宽 / 仪表盘卡片各种尺寸),媒体查询不知道
组件实际可用宽度。
Container Queries(容器查询)让组件按 父容器 尺寸响应式。
2023 后全 evergreen 浏览器支持,可以放心用。
1. 启用容器
.card-container {
container-type: inline-size;
/* 或者 size(同时观察宽 + 高) */
container-name: card; /* 可选,命名容器便于精确引用 */
}
container-type: inline-size 让浏览器观察这个元素的宽度变化,
开销小(不需要观察高度)。
2. 容器查询
@container card (min-width: 400px) {
.card-title { font-size: 1.5rem; }
.card-image { display: block; }
}
@container card (min-width: 600px) {
.card { display: grid; grid-template-columns: 200px 1fr; }
}
@container card 引用前面命名为 "card" 的容器。
不写名字也行:@container (min-width: 400px) 用最近的祖先容器。
3. 完整例子:自适应卡片
<div class="grid">
<div class="card-wrap">
<article class="card">
<img src="thumb.jpg" alt="">
<div>
<h3>标题</h3>
<p>描述...</p>
</div>
</article>
</div>
<div class="card-wrap">
<article class="card">...</article>
</div>
</div>
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.card-wrap {
container-type: inline-size;
}
.card {
display: flex;
flex-direction: column;
gap: 8px;
}
.card img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
/* 容器够宽时改横向布局 */
@container (min-width: 360px) {
.card { flex-direction: row; }
.card img { width: 120px; aspect-ratio: 1; flex-shrink: 0; }
}
视口窄时卡片竖排;视口宽时容器宽度 > 360px,自动横排。
同一组件 + 同一 CSS 在不同上下文行为不同。
4. 容器查询单位
CSS 出了 cqw / cqh / cqi / cqb 等单位,相对于容器尺寸:
.card-title {
font-size: clamp(1rem, 4cqi, 2rem);
}
4cqi = 4% 容器 inline 尺寸。容器越宽字体越大,但限制在 1-2rem 之间。
5. style queries(实验性)
按容器的某个 CSS 自定义属性查询:
.theme-dark { --mode: dark; container-type: normal; }
@container style(--mode: dark) {
.card { background: #1e1e1e; color: #fff; }
}
适合切主题不影响组件 HTML 结构。仍是实验阶段,部分浏览器支持。
6. 与媒体查询配合
媒体查询管页面布局(侧栏开关、导航形态),容器查询管组件内部。
分工清晰:
/* 媒体查询:响应视口 */
@media (max-width: 800px) {
.layout { grid-template-columns: 1fr; }
.sidebar { display: none; }
}
/* 容器查询:响应组件可用空间 */
@container card (min-width: 400px) {
.card { ... }
}
7. 命名 vs 匿名容器
匿名(不写 container-name)查询最近的祖先 container:
.parent { container-type: inline-size; }
@container (min-width: 500px) {
.child { ... }
}
命名让你跨层级精确引用:
.page { container: page / inline-size; }
.card { container: card / inline-size; }
@container page (min-width: 1000px) {
/* 引用 page 容器 */
}
@container card (min-width: 400px) {
/* 引用 card 容器 */
}
8. polyfill / 回退
老浏览器不支持时降级:
.card {
/* 默认(窄屏 / 不支持时的样子) */
flex-direction: column;
}
@container (min-width: 360px) {
.card { flex-direction: row; }
}
/* 或者 @supports 兜底 */
@supports not (container-type: inline-size) {
/* 不支持容器查询的浏览器用媒体查询近似 */
@media (min-width: 600px) {
.card { flex-direction: row; }
}
}
9. 性能
container-type: inline-size 让浏览器为这个元素建立 containment context。
开销很小(不重排不重绘),但避免无脑给所有元素加。
通常每个独立组件根加一个就好。
container-type: size(同时观察宽 + 高)更贵些,因为元素的高度
通常由内容决定,会创建一个潜在的"无限循环"风险。
10. 实际收益
之前没容器查询时,常见 hack:
- 给容器加 class(
.card--wide/.card--narrow)→ 业务代码要知道布局 - ResizeObserver + JS 控制 → 跨框架不一致 + 性能差
- 多套 CSS 类按 prop 切换 → 难维护
容器查询是这些痛点的官方解。组件 truly self-contained。
11. 工具支持
Tailwind CSS v3.4+ 有 container queries plugin:
<div class="@container">
<div class="@md:flex @lg:grid">...</div>
</div>
UI 库(shadcn / Mantine)渐渐采纳。
踩过的坑
- 自己引用自己:
.card { container-type: inline-size; }然后
@container (min-width: ...) .card { width: ... }—— 改 width 会
触发容器尺寸变化 → 触发条件再判断 → 死循环。浏览器有保护但视觉上抖。 - 父子嵌套容器名相同:第二个 container-name 覆盖第一个,意外查询。
跨层 query 务必命名清楚。 - height container query 慎用:必须
container-type: size且容器有
确定高度(不能完全由内容撑开)。 - 把 container-type 加到 body 上 → 全局影响,性能可能下降。粒度
控制在组件根元素。
登录后参与评论。