
用 CSS Container Query 让卡片按容器而不是视口响应
我第一次把同一张“文章推荐卡片”同时放进主栏和侧边栏时,最别扭的不是 CSS 难写,而是页面明明已经很宽,侧边栏里的卡片却仍然只有三百多像素。用 @media (min-width: 1024px) 判断视口后,主栏卡片和侧边栏卡片会一起进入横排模式:前者舒服,后者被挤得标题只剩几字一行。
CSS Container Query 正好解决这个上下文错位。它让卡片根据实际容器的可用宽度切换布局,而不是猜测浏览器窗口有多宽。下面从一张可复用内容卡片出发,用 container-type、container-name 和 @container 完成布局,再补上可访问性、性能和降级处理。
平台资料:https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Containment/Container_queries、https://web.dev/learn/css/container-queries
为什么媒体查询会让可复用卡片失去上下文
媒体查询回答的是“视口或设备满足什么条件”,容器查询回答的是“这个组件所在的祖先容器满足什么条件”。两者都合理,只是观察范围不同。页面级导航、全局字号和整体栅格仍适合媒体查询;会在主栏、侧边栏、弹窗或嵌入区域之间移动的组件,更适合把布局变化绑定到容器。

我现在会先问一个简单问题:如果把组件原样拖到页面另一个区域,它还应该保持当前结构吗?如果答案取决于组件得到的空间,而不是设备类型,那么容器查询通常比再加一组视口断点更贴近真实需求。
| 判断对象 | 更适合的工具 | 典型任务 |
|---|---|---|
| 整个页面或设备视口 | @media | 主导航折叠、全局栅格、打印样式 |
| 组件所在区域的宽度 | @container | 卡片横竖排、工具栏密度、组件字号 |
| 组件内部连续尺度 | 容器查询单位 | 内边距、标题字号、媒体比例微调 |
先让窄容器里的卡片保持自然阅读顺序
我习惯把窄容器样式当成默认值,再用容器查询做增强。这样即使查询条件不匹配,或者旧环境不支持容器查询,卡片仍是一列正常可读的内容,而不是缺少关键布局规则。
这里多包了一层 .story-card-shell。尺寸容器查询通常用于改变查询容器的后代,而不是让容器直接查询自己后再改自己的尺寸。把外层设为查询容器,内层卡片作为被调整的后代,边界会更清楚,也不容易写出互相影响尺寸的规则。
给卡片建立清晰的查询边界
对于横向书写的页面,卡片主要关心可用宽度,所以使用 container-type: inline-size。再给它一个名称 card,后面的查询就不会误匹配页面中其他尺寸容器。

.story-card-shell {
/* 只查询行内轴尺寸;横向书写时通常对应可用宽度。 */
container-type: inline-size;
/* 使用名称限定查询范围,避免命中不相关的祖先容器。 */
container-name: card;
}
.story-card {
/* 窄容器默认纵向排列,旧环境也能获得可读布局。 */
display: grid;
gap: 1rem;
padding: clamp(1rem, 4cqi, 1.5rem);
border: 1px solid #d8dee9;
border-radius: 1rem;
background: #fff;
}
.story-card__media {
/* 清除 figure 的浏览器默认外边距。 */
margin: 0;
}
.story-card__media img {
/* 图片跟随媒体区宽度,并保持稳定比例。 */
display: block;
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 0.75rem;
}
.story-card__body {
/* 文本列允许在 Grid 中收缩,避免长内容撑破容器。 */
min-width: 0;
}
@container card (min-width: 38rem) {
.story-card {
/* 只有名为 card 的容器足够宽时,才切换为横向结构。 */
grid-template-columns: minmax(12rem, 0.8fr) minmax(0, 1.2fr);
align-items: center;
}
.story-card__media img {
/* 横排时提高媒体区纵向占比,避免图片显得过扁。 */
aspect-ratio: 4 / 3;
}
}
断点写成 38rem,不是因为它对应某种设备,而是因为卡片在这个宽度附近能同时容纳可用的图片列和正文列。最稳妥的做法是把组件放进可调宽度的演示容器,缩放到标题、摘要和链接都舒服的位置,再记录这个组件断点。
容器单位让变化更连续,但不要把所有尺寸都绑上去
cqi 表示查询容器行内尺寸的百分之一。示例中的 4cqi 会随卡片容器变宽而增加,再由 clamp() 限定在 1rem 到 1.5rem 之间。它适合处理内边距、间距或有限范围的字号变化,但不应取代清晰的布局断点。
.story-card h2 {
/* 标题只在可读范围内平滑变化,避免宽容器产生夸张字号。 */
font-size: clamp(1.25rem, 1rem + 1.2cqi, 1.8rem);
line-height: 1.25;
overflow-wrap: anywhere;
}
.story-card__eyebrow {
/* 元信息保持稳定,不与主标题争夺视觉层级。 */
font-size: 0.875rem;
color: #52606d;
}
如果没有可用的查询容器,容器查询单位会回退到相应轴的小视口单位。为了避免一个被意外移出容器的组件突然按视口缩放,我仍会给关键值加上下限和上限,并在组件文档里写明外层容器契约。
布局变化不能打乱键盘与读屏顺序
Container Query 只改变视觉布局,不需要复制两套 DOM。图片、标题、摘要和链接的源码顺序在窄卡与宽卡中都保持一致,键盘焦点和读屏顺序也不会因为横竖排切换而跳动。这一点比用 JavaScript 按宽度渲染两套模板更省心。
- 不要为了“左图右文”随意使用会改变视觉顺序的
order,除非源码顺序本身仍然合理。 - 卡片里只有一个主要链接时,给它明确文字;不要把整张卡做成多层嵌套链接。
- 图片提供与内容有关的
alt;纯装饰图应使用空的alt=""。 - 标题很长时使用
min-width: 0和断词规则,不能靠隐藏溢出来掩盖问题。
性能上更重要的是控制边界数量
声明尺寸查询容器会引入相应的尺寸约束,浏览器借此避免后代样式改变祖先尺寸后反复触发条件翻转。对普通卡片而言,这通常不是性能问题;真正需要克制的是把页面上每一层包装元素都声明成容器,导致规则来源难以追踪。
我的取舍是:只在组件可能被不同区域复用、而且内部确实需要响应容器时声明容器;页面大框架继续用媒体查询;组件内连续的小尺寸调整才使用容器单位。这样 DevTools 中看到的边界少,团队也更容易知道一条 @container 到底依赖哪个祖先。
为不支持环境保留可用的窄卡样式
容器查询很适合渐进增强。默认纵排样式已经可以完成阅读任务,支持环境再启用具名容器与横排布局。不要把正文显示、主要链接或表单提交能力放到容器查询内部。
/* 默认样式已经是可用的纵向卡片。 */
.story-card {
display: grid;
grid-template-columns: 1fr;
}
@supports (container-type: inline-size) {
.story-card-shell {
/* 支持容器查询时才建立尺寸查询上下文。 */
container: card / inline-size;
}
@container card (min-width: 38rem) {
.story-card {
/* 增强为横排,不影响不支持环境的基础阅读。 */
grid-template-columns: minmax(12rem, 0.8fr) minmax(0, 1.2fr);
}
}
}
如果项目必须覆盖更旧的浏览器,可以给某些固定布局保留一条媒体查询,但要把它当作近似降级,而不是复制整套容器规则。JavaScript ResizeObserver 更适合驱动图表重算、Canvas 尺寸或无法仅靠 CSS 表达的行为;单纯切换卡片 CSS 布局,没有必要额外维护监听器和 class 状态。
几个容易踩到的边界状态
- 查询容器自己没有变化:
@container规则作用于后代。需要改变外框时,再包一层最清晰。 - 规则命中了错误祖先:匿名查询会寻找最近的合格祖先;复杂页面优先使用
container-name。 - 卡片仍被长文本撑开:给 Grid 或 Flex 子项设置
min-width: 0,再处理 URL、代码和连续字符断行。 - 断点数量越来越多:断点应对应结构变化,不要为每十几个像素写一条规则;连续尺寸交给
clamp()和容器单位。 - 嵌套组件互相干扰:每个组件使用清晰的容器名称,并让规则只描述自己的后代。
适合把哪些组件改成 Container Query
在我实际改造过的组件里,收益最大的通常不是整页,而是卡片、工具栏、筛选面板、评论项和数据摘要块。这些组件会被放进不同宽度的区域,且布局变化由局部空间决定。反过来,如果一个模块始终占满页面,或者变化确实取决于设备与视口,媒体查询依然更直接。
最终判断标准很朴素:页面在问“屏幕有多宽”,组件在问“我现在有多宽”。先把窄布局写成可靠默认值,再给外层建立具名 inline-size 容器,最后只在真正需要结构变化的位置写 @container。这样卡片离开主栏、进入侧边栏或嵌入新页面时,通常不需要再为宿主页面补一组特例。
相关问题
Container Query 可以完全替代媒体查询吗?
不能,也没必要。页面级导航、整体栅格、打印和设备能力仍适合媒体查询;容器查询主要解决可复用组件对局部空间的响应。
为什么更常用 inline-size 而不是 size?
多数卡片只需根据行内轴宽度变化。inline-size 只建立行内尺寸查询上下文,意图更准确;只有确实要同时查询行内轴和块轴尺寸时,才考虑 size。
容器查询断点应该沿用 768px、1024px 吗?
不建议机械沿用设备断点。把组件放进可调宽度容器,以内容开始拥挤或结构可以自然展开的位置作为断点,更符合组件自身需求。
什么时候仍然需要 ResizeObserver?
当尺寸变化要驱动 JavaScript 行为,例如重算图表、更新 Canvas 或同步非 CSS 状态时再用。只是改变 CSS 排列、间距或字号,优先使用容器查询。
Linux cgroup v2 限制服务 CPU 与内存的完整思路
- 上一篇
- Linux cgroup v2 限制服务 CPU 与内存的完整思路
- 下一篇
- 在 ServeMux 中拆分路由、中间件与错误响应
-
- 文章 · 前端 | 3小时前 | 环境变量 vite loadEnv vite.config
- Vite 环境变量为什么在配置加载时取不到
- 133浏览 收藏
-
- 文章 · 前端 | 4小时前 |
- React useOptimistic 怎么在请求失败时回滚列表
- 153浏览 收藏
-
- 文章 · 前端 | 6小时前 | 前端开发 · 前端路由 hostname URLPattern pathname
- URLPattern 怎么同时匹配域名和路径参数
- 108浏览 收藏
-
- 文章 · 前端 | 9小时前 | javascript · JavaScript Intl.DurationFormat 前端国际化 持续时间格式化
- Intl.DurationFormat 怎么本地化显示持续时间
- 141浏览 收藏
-
- 文章 · 前端 | 11小时前 | 前端开发 · 浏览器API · postMessage MessageChannel Web Worker MessagePort
- postMessage 转移 MessagePort 后原端口还能用吗
- 477浏览 收藏
-
- 文章 · 前端 | 12小时前 | html · 前端 · LCP fetchpriority HTMLImageElement.fetchPriority 首屏图片 图片加载优先级
- fetchpriority 怎么只提升首屏关键图片
- 343浏览 收藏
-
- 文章 · 前端 | 15小时前 |
- AbortSignal.any 怎么合并超时和用户取消
- 106浏览 收藏
-
- 文章 · 前端 | 19小时前 | 前端 · 性能优化 · javascript · scheduler.postTask TaskController Prioritized Task Scheduling API TaskSignal JavaScript任务优先级
- Scheduler.postTask 怎么设置任务优先级
- 148浏览 收藏
-
- 文章 · 前端 | 1天前 | 前端 · View Transition API startViewTransition ViewTransitionTypeSet pageswap pagereveal
- View Transition types 怎么为不同导航选择动画
- 195浏览 收藏
-
- 文章 · 前端 | 1天前 | dialog close HTMLDialogElement requestClose
- HTMLDialogElement requestClose 和 close 有什么区别
- 363浏览 收藏
-
- 文章 · 前端 | 1天前 | html · 前端开发 · Popover API popover auto 点击外部关闭 Light Dismiss
- Popover API 怎么实现点击外部自动关闭
- 225浏览 收藏
-
- 文章 · 前端 | 1天前 | 前端 · css · CSS 容器查询 cqi cqb inline-size block-size
- CSS cqi 和 cqb 单位分别跟随哪个容器轴
- 470浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 358次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 416次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 428次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 381次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 207次使用
-
- JavaScript函数定义及示例详解
- 2025-05-11 502浏览
-
- 智能体安全引领产业升级——国内AI安全产品市场深度分析
- 2026-08-21 501浏览
-
- CSS变量简化按钮悬停效果技巧
- 2026-05-31 501浏览
-
- JavaScript符号类型详解与应用
- 2026-05-31 501浏览
-
- HTML剪贴板复制粘贴怎么用
- 2026-05-26 501浏览

