做前端这几年,Flexbox 一直是主力。大部分需求它都能搞定,用着也顺手,基本上遇到布局问题先想的就是 flex。

但有一段时间做管理后台,遇到一个页面布局需求:顶部 header 固定,左侧侧边栏固定宽度,右侧内容区自适应,内容区里面还有一个卡片网格,每行显示 3 个,卡片宽度均等,最后一行如果不满 3 个也要左对齐。

光是最后那个”最后一行左对齐”就让我跟 Flexbox 较劲了好一会儿。后来换成 Grid,两行 CSS 搞定了。从那之后我开始认真学 Grid,越用越顺。

Grid 和 Flex 的核心区别

Flex 是一维布局,它一次只处理一个方向——要么横排,要么竖排。Grid 是二维的,行和列同时管。

这不是说 Grid 比 Flex 好,只是它们擅长的场景不一样。大多数组件内部的布局,Flex 就够了。整体页面结构、卡片网格这类需要同时控制行列的,Grid 更合适。

几个基础概念

先把常用属性过一遍,不用全记,用的时候知道查什么就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.container {
display: grid;

/* 定义列:三列,每列 1fr(等分剩余空间) */
grid-template-columns: 1fr 1fr 1fr;

/* 或者用 repeat 简写 */
grid-template-columns: repeat(3, 1fr);

/* 定义行高 */
grid-template-rows: 60px auto;

/* 间距 */
gap: 16px;
/* 或者分开写 */
column-gap: 20px;
row-gap: 12px;
}

fr 这个单位是 Grid 专属的,表示”剩余空间的比例”。repeat(3, 1fr) 就是三列均分。

几个真实用过的场景

场景一:卡片网格,最后一行左对齐

这个需求用 Flex 做起来麻烦,因为 justify-content: space-between 会让最后一行的孤儿卡片两端拉开。常见的 hack 是加幽灵元素,但很别扭。

Grid 就很直接:

1
2
3
4
5
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}

子元素直接放进去,每行三个,最后一行不满的就自然靠左,不用任何额外处理。宽度全部均等。

场景二:经典后台布局

1
2
3
4
5
6
7
8
9
10
11
12
13
.layout {
display: grid;
grid-template-areas:
"header header"
"sidebar content";
grid-template-columns: 240px 1fr;
grid-template-rows: 60px 1fr;
height: 100vh;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }

grid-template-areas 这个属性很直观,直接用名字”画”出布局结构,改起来也方便。以前我做这种布局喜欢用 position: fixed + margin-left,现在觉得这种写法清晰多了。

场景三:响应式瀑布流(伪)

严格的瀑布流要用 JS,但如果只是卡片高度不一、视觉上错落有致,可以用这个:

1
2
3
4
5
6
7
8
9
.masonry {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 10px; /* 用小行高做基准 */
gap: 10px;
}

/* JS 计算每个 item 需要跨几行 */
/* item.style.gridRowEnd = `span ${rowSpan}` */

这个算是 Grid 和 JS 配合的玩法,CSS 提供网格基础,JS 处理高度计算。

场景四:居中布局(真的很简单)

1
2
3
4
5
.center-wrapper {
display: grid;
place-items: center; /* 水平垂直都居中,相当于 align-items + justify-items */
height: 100vh;
}

比 Flex 还要简洁一点,place-itemsalign-itemsjustify-items 的简写。

auto-fill 和 auto-fit 的区别

这俩容易混,记一次:

1
2
3
4
5
/* auto-fill:尽可能多创建列,即使列是空的 */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));

/* auto-fit:把剩余空间分给已有的列,不留空列 */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

元素比较多时看不出区别,元素少的时候差异就出来了。auto-fit 会让少数几个元素撑满整行,auto-fill 会在右边留出空格。

大多数响应式卡片布局用 auto-fill 更好,视觉更自然。

浏览器兼容性

现在主流浏览器都支持得很好了,Chrome、Firefox、Safari、Edge 基本没问题。如果要兼容 IE,那就只能放弃了,IE 支持的是老版本 Grid 规范,属性名都不一样,基本不可用。

什么时候用 Grid,什么时候用 Flex

我现在的习惯:

  • 页面级结构(header/sidebar/content)→ Grid
  • 卡片网格、图片列表 → Grid
  • 组件内部的对齐(按钮组、表单项、导航链接)→ Flex
  • 单行内容的排列 → Flex

两个不冲突,嵌套着用也完全没问题,外层 Grid 定结构,内层 Flex 处理细节。

Grid 的学习曲线比 Flex 稍陡一点,属性多,但核心用法其实就那几个,熟了之后很多布局需求会变得特别简单。建议可以先在小项目里用用,找找感觉。