CSS 布局:Flex 和 Grid 怎么选,附常见坑
做前端布局时,最常纠结的问题不是"怎么写",而是"该用 Flex 还是 Grid"。一句话先说结论:**一维布局用 Flex,二维布局用 Grid**。一维指只沿一个方向排:横着一排或竖着一列;二维指行和列都要同时控制,比如整体页面骨架、卡片墙。
一、Flex:管"一排"或"一列"
Flex 适合导航栏、按钮组、头像加文字、垂直居中这类场景。容器定方向,子项沿主轴排。
```html
<nav class="nav">
<a href="#">首页</a>
<a href="#">文章</a>
<a href="#">关于</a>
<span class="spacer"></span>
<button>登录</button>
</nav>
```
```css
.nav {
display: flex;
align-items: center; /* 交叉轴居中 */
gap: 16px;
}
.spacer {
flex: 1; /* 把登录按钮顶到最右边 */
}
```
再看一个经典需求:未知高度的盒子水平垂直居中。
```css
.center {
display: flex;
justify-content: center; /* 主轴居中 */
align-items: center; /* 交叉轴居中 */
min-height: 200px;
}
```
记住一对容易混的概念:`justify-content` 管主轴,`align-items` 管交叉轴。默认主轴是横向,所以"水平居中"是 `justify-content: center`,改成 `flex-direction: column` 后,主轴变竖向,两者的作用就互换了。
二、Grid:管"行列一起排"
Grid 适合页面骨架、卡片列表、表单两列对齐。先画格子,再把内容放进格子。
```html
<div class="layout">
<header>头部</header>
<aside>侧边栏</aside>
<main>正文</main>
<footer>页脚</footer>
</div>
```
```css
.layout {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"aside main"
"footer footer";
gap: 16px;
}
header { grid-area: header; }
aside { grid-area: aside; }
main { grid-area: main; }
footer { grid-area: footer; }
```
卡片自适应列数,不用写媒体查询也能"能排几列排几列":
```css
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
```
`minmax(220px, 1fr)` 的意思是:每列最窄 220px,有剩余空间时按 1fr 平分。`auto-fill` 会先把格子占满,`auto-fit` 则会把空列折叠掉、让已有卡片拉宽,做"不足一行时拉满"用 `auto-fit` 更合适。
三、对照表:怎么选
| 场景 | 选谁 | 原因 |
|---|---|---|
| 导航栏、工具条、按钮组 | Flex | 只有一排,间距和对齐一把梭 |
| 垂直居中、两端对齐 | Flex | 主轴加交叉轴两个属性就够 |
| 页面骨架、左右加上下结构 | Grid | 行列、区域一次定义清楚 |
| 卡片墙、图库 | Grid | 列数、间距、对齐由格子统一管 |
| 表单标签加输入框对齐 | Grid | 两列对齐比 Flex 套娃更稳 |
| 内容数量不确定、只管横向流动 | Flex + flex-wrap | 换行后按内容自然流动 |
拿不准时可以这样判断:先问"行和列是否都要对齐"。只要答案是"是",就优先 Grid;只关心一排或一列的顺序和间距,就用 Flex。两者也可以嵌套:外层 Grid 定骨架,内层 Flex 管一排里的对齐,这是实际项目里最常见的组合。
四、常见坑
1. Flex 子项被内容撑爆,挤不回去
长单词或长链接会把 Flex 子项撑宽,`flex: 1` 也压不住。原因是 Flex 子项默认 `min-width: auto`,不会小于内容宽度。解法是给可能溢出的子项加 `min-width: 0`:
```css
.item {
flex: 1;
min-width: 0; /* 允许收缩,配合 overflow 截断才有效 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
```
2. 忘了 flex-wrap,内容溢出容器
默认 `flex-wrap: nowrap`,子项再多也不会换行,只会挤压或溢出。做标签、筛选条件这种数量不定的横排时,记得写 `flex-wrap: wrap`,并用 `gap` 控制间距,不要再靠子项 margin 硬凑。
3. flex: 1 不是 flex-grow: 1
`flex: 1` 等价于 `flex: 1 1 0%`,基准是 0,会按比例重新分配全部空间;而只写 `flex-grow: 1` 时,`flex-basis` 仍是 `auto`,先按内容宽度占位,再分剩余空间。两者在内容长短不一时结果差别很大,想等分就写 `flex: 1`。
4. Grid 里 fr 不是百分比
`1fr` 分的是"剩余空间",不是容器总宽度的固定比例。`grid-template-columns: 200px 1fr` 里,1fr 拿到的是减去 200px 和 gap 之后的部分;如果列里有很长的内容,`minmax(0, 1fr)` 才能防止该列被内容撑大。
5. auto-fill 和 auto-fit 用反
两者只在"格子多于内容"时有区别:`auto-fill` 保留空列,卡片保持原宽、右边留白;`auto-fit` 折叠空列,卡片拉宽填满一行。想要"卡片墙不满一行也铺满"选 `auto-fit`,想要"固定列宽、右边留白"选 `auto-fill`。
6. 居中只写一半
`justify-content` 和 `align-items` 只管容器里的子项整体位置;Grid 里想让某个格子里的内容居中,用 `place-items: center`(等于 `align-items` 加 `justify-items`)更直接。调试时先确认:你居中的是"子项在容器里的位置",还是"内容在格子里的位置",两者属性不一样。
五、收尾检查清单
发布前对着过一遍:方向是单维还是双维;Flex 容器有没有按需写 `flex-wrap`;可能溢出的子项有没有 `min-width: 0`;Grid 列宽用的是 `fr` 还是固定值、需不需要 `minmax`;`auto-fill` 还是 `auto-fit` 是否符合预期。选对工具,布局代码会短一半,返工也会少一半。