做后台管理系统、商品列表或者博客首页时,卡片布局几乎是绕不开的形态。传统做法是浮动加百分比宽度,后来是flex布局手动换行,再后来大家发现CSS Grid才是这类需求的天生解决方案。尤其是repeat(auto-fit, minmax())这个组合,可以让卡片数量随容器宽度自动增减,几乎一行代码就替代了整套media query断点。不过很多初学者只是照抄代码,并不清楚auto-fit和auto-fill的区别,也不知道什么时候还需要media query来补充。这篇文章就把这些细节彻底讲清楚。

CSS Grid卡片布局的基础写法
先从最基础的固定列数网格说起。Grid布局的核心思想是把容器划分成行和列,子项自动落入格子中。定义一个三列的卡片容器只需要两个属性:
.card-container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 三等分列 */
gap: 16px; /* 卡片之间的间距 */
}
.card {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}这里的repeat(3, 1fr)表示重复三列,每列占一份可用空间,gap则同时控制行间距和列间距。这个写法在桌面端没问题,但窗口缩小后三列卡片会挤成一团。要让列数随宽度变化,最直接的反应是加media query:
.card-container {
display: grid;
grid-template-columns: 1fr; /* 默认单列 */
gap: 16px;
}
@media (min-width: 600px) {
.card-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 900px) {
.card-container {
grid-template-columns: repeat(3, 1fr);
}
}这种方式直观易懂,但缺点也很明显:断点是写死的,如果卡片容器不是占满整个视口(比如嵌套在侧边栏旁边的主体区域里),基于视口宽度的min-width判断就会失准。容器明明只剩下500像素,视口却有1200像素,布局依然按三列渲染,卡片被压得没法看。
auto-fit与minmax:让浏览器自己算列数
Grid规范里有一组非常强大的函数式列定义,核心是repeat()配合auto-fit或auto-fill,再加上minmax()限定每列的尺寸范围。先看最常用的写法:
.card-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}这句话的含义是:每一列的宽度最小260像素、最大平分剩余空间,列的数量由容器宽度自动决定。浏览器会拿容器宽度去除以260像素这个最小值,能塞几列就塞几列,塞不下就自动降级。容器600像素宽时是两列,900像素宽时是三列,1300像素宽时是五列,全程不需要一行media query。
这里必须讲清楚auto-fit和auto-fill的区别,这是面试和实战中的高频坑点。两者都会根据容器宽度生成尽可能多的轨道,区别在于空轨道的处理方式:auto-fill会保留所有生成的空轨道,即使里面没有内容,这些空轨道依然占据布局空间;而auto-fit会把空的轨道折叠为零宽度,让已有的卡片拉伸填满整行。
举个例子,容器宽1000像素,minmax最小值260像素,理论上能生成三列轨道,但实际只有两张卡片。用auto-fill时,第三列空轨道还在,两张卡片各占约三分之一宽度,右侧留出一块空白区域;用auto-fit时,空轨道被折叠,两张卡片各占一半,把整行铺满。做卡片列表时多数情况希望卡片铺满容器,所以auto-fit更常用;而在设计上刻意希望卡片保持固定宽度、宁可留白的情况下,auto-fill反而是正确选择。
还有一个细节值得注意:minmax()的第二个参数用1fr意味着卡片会拉伸填满,如果你希望卡片宽度固定不变、只是数量自适应,可以写成minmax(260px, 260px)或者直接repeat(auto-fill, 260px),两种写法效果接近,语义略有差异。
auto-fit与media query结合:弹性与可控并存
auto-fit解决了列数自适应的问题,但响应式设计不只是列数。卡片内部的字号、内边距、图片高度在大小屏之间往往需要不同的值,这时候media query就派上用场了。两者并不冲突,推荐的分工是:列数交给auto-fit自动计算,视觉细节交给media query调整。
.card-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
padding: 20px;
}
.card img {
width: 100%;
aspect-ratio: 16 / 9; /* 固定图片比例,防止高度参差不齐 */
object-fit: cover;
border-radius: 8px 8px 0 0;
}
.card h3 {
font-size: 1rem;
margin: 12px 0 8px;
}
.card p {
font-size: 0.875rem;
color: #666;
line-height: 1.6;
}
/* 小屏幕收紧间距和字号 */
@media (max-width: 600px) {
.card-container {
gap: 12px;
padding: 12px;
}
.card h3 {
font-size: 0.9375rem;
}
}
/* 大屏幕放宽内边距,卡片更透气 */
@media (min-width: 1200px) {
.card {
padding: 24px;
}
.card h3 {
font-size: 1.125rem;
}
}这套组合拳的思路是:无论视口多宽,列数始终由minmax(280px, 1fr)这个最小卡片宽度决定,media query只负责微调间距、字号这些和容器宽度无关的全局视觉变量。这样写出来的样式表结构清晰,断点数量也从四五个降到一两个,后期维护成本明显降低。
如果卡片容器本身嵌套在复杂布局里,还可以考虑容器查询。给容器开启container-type: inline-size之后,内部的@container规则会基于容器实际宽度而不是视口宽度生效,彻底解决嵌套场景下media query判断失准的问题:
.card-container {
container-type: inline-size;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
@container (min-width: 700px) {
.card h3 {
font-size: 1.125rem;
}
}目前主流浏览器对容器查询的支持已经比较完善,新项目可以放心使用,需要兼容老旧浏览器的项目则以auto-fit加media query为主更稳妥。总结一下:先用repeat(auto-fit, minmax())搞定列数自适应,记得区分auto-fit折叠空轨道、auto-fill保留空轨道的行为差异,再用少量media query或容器查询处理字号间距等细节,一套既能自动伸缩又足够可控的响应式卡片布局就成型了。