概念
- Grid(二维布局) :原生支持"行 × 列",擅长"规则网格""一排 N 个""最后一行自然靠左且间距统一"。
- Flex(一维布局) :擅长在单行或单列内分配空间;多行时每一行各自独立,容易出现"最后一行两端拉扯"或对齐不齐。
- CSS2 传统方法(float / inline-block / position) :历史包袱大、需要各种 hack;能实现,但维护成本高、弹性差。
原理
-
Grid 轨道(track) :
grid-template-columns
定义列轨道;repeat(auto-fit, minmax(X, 1fr))
会尽可能多塞列,每列至少 X 宽,最多分到 1fr 的剩余空间。auto-fit
:把空轨道折叠掉,视觉上会"贴左对齐"。auto-fill
:保留空轨道(不可见,但占位),某些布局会更易控。
-
Flex 伸缩 :主轴上对子项分配剩余空间;
flex-wrap: wrap
换行后,"每一行"是各自独立的 flex 行,导致多行对齐难。 -
CSS2:
float
脱离常规流、需要"清除浮动";inline-block
会产生字间空隙;- 绝对定位失去自适应,通常只做局部装饰用。
对比(要点速览)
维度 | Grid | Flex | CSS2(float/inline-block) |
---|---|---|---|
布局维度 | 二维(行+列) | 一维(行或列) | 近似一维/靠技巧 |
多行对齐 | 天然规整 | 行与行独立,易"最后一行不齐" | 需要 hack/细算宽度 |
间距 | gap 原生、行列一致 |
gap 原生(现代浏览器 OK) |
多靠 margin ,会有合并/溢出 |
响应式 | minmax + auto-fit/fill 很优雅 |
常要 calc() + 媒体查询 |
百分比 + 负外边距等旧技巧 |
复杂度 | 语义清晰、可读性好 | 中等 | 维护成本高 |
你的痛点映射:
- "一排三个 & 自动对齐 & 最后一行不要被拉扯" → Grid 最优。
- "Flex 两端对齐导致最后一行被拉开,竖向不齐" → 换用
flex-start + gap
或改用 Grid。 - "padding/margin 造成边距问题" → 统一用
gap
(Grid/Flex 都支持),尽量少用水平外边距叠加。
实践(含逐行注释)
A. Grid 方案(固定 3 列,简洁稳妥)
ini
<div class="g3">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
<div class="card">5</div>
</div>
css
.g3 {
margin-top: 42px; /* 你的需求里的外边距 */
display: grid; /* 启用 Grid */
grid-template-columns: repeat(3, minmax(0, 1fr));
/* ↑ 固定 3 列;minmax(0,1fr) 防止内容过长撑破列宽 */
gap: 20px; /* 统一行/列间距,不再用 margin 叠加 */
}
.card {
background: #e8f3ff; /* 示例背景 */
padding: 16px; /* 卡片内边距 */
border-radius: 8px;
/* 默认 align-items: stretch,保证一行内等高(以该行最高为准) */
}
特点 :始终 3 列,最后一行自然靠左且间距一致;无需计算 calc()
;适合"大屏固定列数"的列表页。
B. Grid 方案(自适应列数:最小卡片宽度 280px)
xml
<div class="g-auto">
<!-- 放任意数量的卡片 -->
<div class="card">1</div><div class="card">2</div><div class="card">3</div>
<div class="card">4</div><div class="card">5</div><div class="card">6</div>
</div>
css
.g-auto {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
/* ↑ 每列至少 280px,空间够就多塞一列;不够就自动掉列 */
/* auto-fit 会折叠空列,最后一行会自然左对齐(视觉更舒服) */
}
如果你想"更像卡片墙"的体验,通常用 固定最小宽度 (像 240/280/320px)比用百分比(30%)更稳,因为百分比还要把
gap
算进去,容易出现刚好不满 3 列的临界抖动。
C. Flex 方案(演示常见坑 & 修复)
❌ 常见问题写法(最后一行被拉开)
css
.flex-bad {
display: flex;
flex-wrap: wrap; /* 允许换行 */
justify-content: space-between;/* ❌ 两端对齐:最后一行会被撑开难看 */
gap: 20px; /* 现代浏览器支持 flex-gap,但配合 space-between 仍有最后一行问题 */
}
.flex-bad > .item {
flex: 0 1 30%; /* 看似 3 列,但没考虑 gap 与临界宽 */
}
✅ 修复 1:改为靠左 + 用 gap 控间距
css
.flex-good {
display: flex;
flex-wrap: wrap; /* 换行 */
justify-content: flex-start; /* ✅ 靠左排列,最后一行不再被拉开 */
gap: 20px; /* ✅ 统一间距 */
}
.flex-good > .item {
/* 三列公式:((100% - 2*gap) / 3),2*gap 是一行内两个间距 */
flex: 0 1 calc((100% - 2*20px) / 3); /* 需要把 20px 写进 calc */
/* 或者使用 CSS 变量:--g:20px; calc((100% - 2*var(--g))/3) */
box-sizing: border-box; /* 防止 padding 参与宽度导致换行抖动 */
}
✅ 修复 2:响应式(最小宽度方案)
css
.flex-auto {
--min: 280px; /* 最小卡片宽度 */
display: flex;
flex-wrap: wrap;
justify-content: flex-start; /* 保持靠左 */
gap: 20px;
}
.flex-auto > .item {
/* 当容器足够宽时,会挤出更多列;不够时掉列 */
flex: 1 1 var(--min); /* "至少占 --min,剩余平均分" 的近似效果 */
max-width: calc((100% - 2*20px) / 3); /* 避免无限拉伸,限制到 3 列上限(可选) */
}
结论:Flex 可以做,但需要一堆小心思;Grid 更"开箱即用"。
D. CSS2 方案(备用与迁移)
1) float(含清除浮动)
xml
<div class="float-3 clearfix">
<div class="col">1</div><div class="col">2</div><div class="col">3</div>
<div class="col">4</div><div class="col">5</div>
</div>
css
.float-3 {
margin-top: 42px;
/* 用负外边距抵消子项 margin 的"行末溢出",是老派写法之一 */
margin-left: -10px; /* = 子项的左 margin */
}
.float-3 .col {
float: left; /* 关键:让列并排 */
width: calc((100% - 2*20px) / 3); /* 三列宽度 + 手动扣 gap(复杂且易抖动) */
margin-left: 10px; /* 横向间距的一半(左右合起来 20px) */
margin-bottom: 20px; /* 纵向间距 */
box-sizing: border-box;
background: #f6f6f6; padding: 16px; border-radius: 8px;
}
/* 清除浮动 */
.clearfix::after { content: ""; display: table; clear: both; }
缺点:
- 需要清除浮动;
- 间距要靠
margin
手算,容器要配负外边距抵消; - 断点/换行/等高都不优雅。
2) inline-block(解决空白缝隙)
xml
<div class="ib-3">
<!-- 去掉换行/空格,或用注释/字体归零来消除间隙 -->
<div class="cell">1</div><div class="cell">2</div><div class="cell">3</div>
<div class="cell">4</div><div class="cell">5</div>
</div>
css
.ib-3 {
font-size: 0; /* 关键:清除 inline-block 之间的空白字符间隙 */
}
.ib-3 .cell {
display: inline-block;
vertical-align: top; /* 避免基线错位 */
width: calc((100% - 2*20px)/3); /* 三列 + 手算 gap(同样麻烦) */
margin-right: 20px; /* 间距 */
margin-bottom: 20px;
font-size: 14px; /* 恢复文字大小 */
background: #fff7e6; padding: 16px; border-radius: 8px; box-sizing: border-box;
}
.ib-3 .cell:nth-child(3n) { margin-right: 0; } /* 每行最后一个去掉右间距 */
缺点:靠技巧实现,易碎、难维护;不推荐新项目使用。
拓展
- auto-fit vs auto-fill
auto-fit
折叠空轨道,最后一行看起来"贴左";auto-fill
保留空轨道(不可见),某些网格对齐需求更好算。
你的场景通常选 auto-fit,视觉更自然。
- 用
gap
取代margin
做栅格间距
gap
不会引起外边距合并;语义更清晰;Grid/Flex 均支持(现代浏览器)。
- 防止内容把列撑爆
- 列宽写
minmax(0, 1fr)
; - 文本加
overflow-wrap:anywhere;
或word-break:break-word;
。
- 统一卡片高度
- Grid/Flex 默认同一行会以最高项为基准拉伸;
- 若需卡片内部"头-体-脚"布局:给
.card
用display:flex; flex-direction:column;
,中间内容flex:1
,按钮区自然贴底。
- 响应式更优雅
-
常见写法:
grid-template-columns: repeat(auto-fit, minmax(clamp(240px, 25vw, 320px), 1fr));
clamp(最小, 理想, 最大)
让卡片宽度随视口柔性变化。
- 容器查询(Container Query) (现代浏览器)
- 让组件而非视口决定断点:
css
.wrapper { container-type: inline-size; }
@container (min-width: 900px) {
.list { grid-template-columns: repeat(3, 1fr); }
}