容器查询(Container Queries)是 CSS 最受期待的特性之一。它让组件可以根据父容器的尺寸来调整样式,而不是依赖视口宽度。这对构建真正可复用的响应式组件至关重要。
为什么需要容器查询#
传统媒体查询的问题:
/* 媒体查询基于视口宽度 */@media (min-width: 768px) { .card { flex-direction: row; }}同一个卡片组件可能出现在不同宽度的容器中:
- 全宽主内容区
- 窄侧边栏
- 弹窗模态框
但媒体查询无法区分这些场景——它只知道视口宽度。
容器查询解决了这个问题:
/* 基于容器宽度 */@container (min-width: 400px) { .card { flex-direction: row; }}基本用法#
定义容器#
使用 container-type 将元素标记为容器:
.card-container { container-type: inline-size;}container-type 值:
size:查询宽度和高度inline-size:只查询内联方向尺寸(通常是宽度)normal:默认值,不是查询容器
编写容器查询#
@container (min-width: 400px) { .card { display: flex; gap: 1rem; }
.card-image { width: 150px; flex-shrink: 0; }}完整示例#
.card-wrapper { container-type: inline-size;}
.card { padding: 1rem; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);}
.card-image { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px;}
.card-content { margin-top: 1rem;}
/* 容器宽度 >= 400px 时水平布局 */@container (min-width: 400px) { .card { display: flex; gap: 1.5rem; }
.card-image { width: 200px; aspect-ratio: 1; }
.card-content { margin-top: 0; flex: 1; }}
/* 容器宽度 >= 600px 时更大的图片 */@container (min-width: 600px) { .card-image { width: 300px; }}命名容器#
当有多个嵌套容器时,使用名称区分:
.sidebar { container-type: inline-size; container-name: sidebar;}
.main { container-type: inline-size; container-name: main;}
/* 简写 */.sidebar { container: sidebar / inline-size;}
/* 查询特定容器 */@container sidebar (min-width: 300px) { .widget { padding: 1.5rem; }}
@container main (min-width: 800px) { .article { columns: 2; }}容器查询单位#
容器查询引入了新的相对单位:
| 单位 | 描述 |
|---|---|
cqw | 容器宽度的 1% |
cqh | 容器高度的 1% |
cqi | 容器内联尺寸的 1% |
cqb | 容器块尺寸的 1% |
cqmin | cqi 和 cqb 中较小者 |
cqmax | cqi 和 cqb 中较大者 |
.container { container-type: inline-size;}
.title { font-size: clamp(1rem, 5cqi, 2rem);}
.hero { height: 50cqh;}样式查询(实验性)#
除了尺寸,还可以查询容器的样式值:
.card { --theme: light;}
@container style(--theme: dark) { .card-content { color: white; }}🔶 样式查询目前仅部分浏览器支持。
实战案例#
响应式卡片组件#
.card-container { container-type: inline-size;}
.card { display: grid; gap: 1rem; padding: 1rem; background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);}
.card-image { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px;}
.card-title { font-size: 1.125rem; font-weight: 600; margin: 0;}
.card-description { color: #6b7280; margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;}
/* 中等宽度:水平布局 */@container (min-width: 350px) { .card { grid-template-columns: 120px 1fr; align-items: start; }
.card-image { aspect-ratio: 1; }}
/* 大宽度:更大图片,显示更多内容 */@container (min-width: 500px) { .card { grid-template-columns: 200px 1fr; padding: 1.5rem; gap: 1.5rem; }
.card-title { font-size: 1.25rem; }
.card-description { -webkit-line-clamp: 3; }}自适应导航#
.nav-container { container-type: inline-size;}
.nav { display: flex; align-items: center; padding: 1rem;}
.nav-logo { font-weight: bold;}
.nav-links { display: none; gap: 1rem; margin-left: auto;}
.nav-toggle { margin-left: auto;}
/* 容器足够宽时显示链接 */@container (min-width: 600px) { .nav-links { display: flex; }
.nav-toggle { display: none; }}仪表板小部件#
.widget-container { container-type: inline-size;}
.widget { padding: 1rem; background: white; border-radius: 8px;}
.widget-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;}
.widget-title { font-size: 0.875rem; font-weight: 600; margin: 0;}
.widget-value { font-size: 1.5rem; font-weight: bold;}
.widget-chart { display: none; height: 100px;}
/* 小部件够大时显示图表 */@container (min-width: 200px) { .widget { padding: 1.5rem; }
.widget-value { font-size: 2rem; }}
@container (min-width: 300px) { .widget-chart { display: block; }}响应式表格#
.table-container { container-type: inline-size; overflow-x: auto;}
.table { width: 100%; border-collapse: collapse;}
.table th,.table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #e5e7eb;}
/* 小容器:隐藏次要列 */.table .priority-low { display: none;}
@container (min-width: 500px) { .table .priority-low { display: table-cell; }}
/* 非常小的容器:卡片式布局 */@container (max-width: 400px) { .table, .table tbody, .table tr, .table td { display: block; }
.table thead { display: none; }
.table tr { margin-bottom: 1rem; border: 1px solid #e5e7eb; border-radius: 8px; padding: 1rem; }
.table td { border: none; padding: 0.25rem 0; }
.table td::before { content: attr(data-label); font-weight: 600; margin-right: 0.5rem; }}与媒体查询配合#
容器查询不是替代媒体查询,而是补充:
/* 媒体查询:整体布局 */@media (min-width: 768px) { .layout { display: grid; grid-template-columns: 250px 1fr; }}
/* 容器查询:组件级响应式 */.sidebar { container-type: inline-size;}
@container (min-width: 200px) { .sidebar-widget { padding: 1.5rem; }}最佳实践#
从组件层面思考#
/* 组件自己的容器定义 */.card { container-type: inline-size;}
/* 组件内部的响应式规则 */@container (min-width: 300px) { .card-inner { /* ... */ }}渐进增强#
/* 基础样式(不支持容器查询的浏览器) */.card { display: block;}
/* 支持容器查询时 */@supports (container-type: inline-size) { .card-container { container-type: inline-size; }
@container (min-width: 400px) { .card { display: flex; } }}避免过度嵌套#
/* 避免:过多嵌套容器 */.a { container-type: inline-size;}.b { container-type: inline-size;}.c { container-type: inline-size;}
/* 推荐:在需要的层级定义容器 */.card-grid { container-type: inline-size;}浏览器兼容性#
| 浏览器 | 支持版本 |
|---|---|
| Chrome | 105+ |
| Firefox | 110+ |
| Safari | 16+ |
| Edge | 105+ |
样式查询(@container style())目前仅 Chrome 111+ 支持。
容器查询是 CSS 响应式设计的重大突破。它让组件真正独立于放置位置,可以根据自身容器来调整布局。结合媒体查询,你可以构建既适应视口又适应容器的完美响应式体验。
这是 CSS 系列的最后一篇。希望这 15 篇文章能帮助你建立完整的 CSS 知识体系,从基础的选择器、盒模型,到现代的 Flexbox、Grid、容器查询,再到动画、变换、滤镜等视觉效果。CSS 是一门值得深入学习的技术,祝你在前端开发之路上越走越远!