Skip to content

Flex & Grid ​

面试必问的两种现代布局方案。Flex 适合一维排列,Grid 适合二维网格。每个属性都附带视觉效果示意。


Flex 布局 ​

核心概念 ​

┌─────────────────────────────────────────────────────┐
│  Flex Container (display: flex)                     │
│                                                     │
│  ←─────────────── 主轴 main axis ───────────────→   │
│  ┌─────┐ ┌─────┐ ┌─────┐                           │
│  │  1  │ │  2  │ │  3  │   ↑ 交叉轴 cross axis     │
│  └─────┘ └─────┘ └─────┘   ↓                       │
│                                                     │
└─────────────────────────────────────────────────────┘

默认:主轴 = 水平方向(左→右),交叉轴 = 垂直方向(上→下)

容器属性 ​

flex-direction — 决定主轴方向 ​

css
.container { display: flex; }
值主轴方向效果
row(默认)左 → 右[1] [2] [3]
row-reverse右 → 左[3] [2] [1]
column上 → 下纵向排列
column-reverse下 → 上纵向逆序
css
/* row(默认) */
flex-direction: row;
/*
┌──────────────────────────┐
│ [1]  [2]  [3]            │  ← 从左到右
└──────────────────────────┘
*/

/* column */
flex-direction: column;
/*
┌─────┐
│ [1] │
│ [2] │   ← 从上到下
│ [3] │
└─────┘
*/

/* row-reverse */
flex-direction: row-reverse;
/*
┌──────────────────────────┐
│            [3]  [2]  [1] │  ← 从右到左,顺序颠倒
└──────────────────────────┘
*/

justify-content — 主轴方向对齐 ​

假设容器宽 600px,3 个子项各 100px:

css
/* flex-start(默认):靠左 */
justify-content: flex-start;
/*
┌──────────────────────────────────────────────┐
│ [1]  [2]  [3]                                │
└──────────────────────────────────────────────┘
*/

/* flex-end:靠右 */
justify-content: flex-end;
/*
┌──────────────────────────────────────────────┐
│                                [1]  [2]  [3] │
└──────────────────────────────────────────────┘
*/

/* center:居中 */
justify-content: center;
/*
┌──────────────────────────────────────────────┐
│              [1]  [2]  [3]                   │
└──────────────────────────────────────────────┘
*/

/* space-between:两端对齐,中间均分 */
justify-content: space-between;
/*
┌──────────────────────────────────────────────┐
│ [1]                  [2]                  [3] │
└──────────────────────────────────────────────┘
    ↑                    ↑                    ↑
  贴左边              正中间               贴右边
*/

/* space-around:每个子项左右 margin 相等 */
justify-content: space-around;
/*
┌──────────────────────────────────────────────┐
│  ■ [1] ■          ■ [2] ■          ■ [3] ■  │
└──────────────────────────────────────────────┘
  ■ = 等距 margin,相邻子项间距是边缘的 2 倍
*/

/* space-evenly:所有间距相等(含边缘) */
justify-content: space-evenly;
/*
┌──────────────────────────────────────────────┐
│ ■  [1]  ■  [2]  ■  [3]  ■                  │
└──────────────────────────────────────────────┘
  所有 ■ 间距相等
*/

align-items — 交叉轴对齐(单行) ​

假设容器高 200px:

css
align-items: stretch;  /* 默认:拉伸填满 */
/*
┌──────────────────┐
│ ┌──────────────┐ │
│ │ [1]          │ │  ← 没设高度 → 撑满容器
│ │              │ │
│ ├──────────────┤ │
│ │ [2]          │ │
│ │              │ │
│ └──────────────┘ │
└──────────────────┘
*/

align-items: flex-start;  /* 顶部对齐 */
/*
┌──────────────────┐
│ ┌──┐ ┌──┐ ┌──┐   │
│ │1 │ │2 │ │3 │   │  ← 都贴顶部
│ └──┘ └──┘ └──┘   │
│                  │
│                  │
└──────────────────┘
*/

align-items: flex-end;  /* 底部对齐 */
/*
┌──────────────────┐
│                  │
│                  │
│ ┌──┐ ┌──┐ ┌──┐   │  ← 都贴底部
│ │1 │ │2 │ │3 │   │
│ └──┘ └──┘ └──┘   │
└──────────────────┘
*/

align-items: center;  /* 居中 */
/*
┌──────────────────┐
│                  │
│ ┌──┐ ┌──┐ ┌──┐   │  ← 垂直居中
│ │1 │ │2 │ │3 │   │
│ └──┘ └──┘ └──┘   │
│                  │
└──────────────────┘
*/

align-items: baseline;  /* 基线对齐 */
/*
┌────────────────────────┐
│  大字     小字   12px  │  ← 按文字基线对齐
│ ┌──┐    ┌──┐  ┌──┐    │
│ │A │    │b │  │c │    │
│ └──┘    └──┘  └──┘    │
└────────────────────────┘
*/

flex-wrap + align-content — 多行对齐 ​

当子项宽度总和超过容器:

css
/* nowrap(默认):不换行,子项等比缩小 */
flex-wrap: nowrap;
/*
┌──────────────────────┐  容器 400px
│ [浅][深][浅][深][浅] │  5 个各 100px → 缩小到各 80px
└──────────────────────┘
*/

/* wrap:换行 */
flex-wrap: wrap;
/*
┌──────────────────────┐
│ [1] [2] [3] [4]       │  ← 第一行(4 个放得下)
│ [5]                   │  ← 第二行(放不下,换行)
└──────────────────────┘
*/

align-content 控制多行时行与行之间的间距(类似 justify-content 的纵向版):

css
align-content: space-between;
/*
┌──────────────────────┐
│ [1] [2] [3]          │  ← 贴顶
│                      │
│ [4] [5]              │  ← 贴底
└──────────────────────┘
*/

align-content: center;
/*
┌──────────────────────┐
│                      │
│ [1] [2] [3]          │  ← 整体居中
│ [4] [5]              │
│                      │
└──────────────────────┘
*/

⚠️ align-content 只在多行(flex-wrap: wrap)时生效。单行用 align-items。


项目属性(子元素) ​

flex-grow — 瓜分剩余空间 ​

容器 600px,A 和 B 各 100px,剩余 400px:

css
/* A flex-grow:1  B flex-grow:2 */
/* A 分 1/3 剩余 = 133px,最终 100+133 = 233px */
/* B 分 2/3 剩余 = 267px,最终 100+267 = 367px */
/*
┌──────────────────────────────────────────────────┐
│ ┌──────────────────┐  ┌────────────────────────┐ │
│ │  A(233px)       │  │  B(367px)            │ │
│ └──────────────────┘  └────────────────────────┘ │
└──────────────────────────────────────────────────┘
*/

/* 全设 flex-grow: 1 → 等分 */
/*
┌──────────────────────────────────────────────────┐
│ ┌─────────────────┐ ┌─────────────────┐          │
│ │  A(300px)      │ │  B(300px)      │         │
│ └─────────────────┘ └─────────────────┘          │
└──────────────────────────────────────────────────┘
*/

flex-shrink — 空间不够时谁缩 ​

容器 300px,A 200px + B 300px,溢出 200px:

css
/* A flex-shrink:1  B flex-shrink:2 */
/* 总权重 = 1×200 + 2×300 = 800 */
/* A 收缩 200 × 200/800 = 50px → 最终 150px */
/* B 收缩 200 × 600/800 = 150px → 最终 150px */
/*
┌──────────────────────────────────────┐ 容器 300px
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ A(150px)   │ │ B(150px)       │ │
│ └─────────────┘ └─────────────────┘ │
└──────────────────────────────────────┘
*/

/* A flex-shrink:0  B flex-shrink:1 */
/* A 不缩 = 200px,B 独占剩余 100px */
/*
┌──────────────────────────────────────┐ 容器 300px
│ ┌─────────────────┐ ┌───┐           │
│ │ A(200px 不缩)  │ │B  │  ← 100px │
│ └─────────────────┘ └───┘           │
└──────────────────────────────────────┘
*/

flex 简写 — 最常用的三个值 ​

css
/* flex: 1 = flex-grow:1, flex-shrink:1, flex-basis:0% */
/* 从 0 开始分配 → 不管内容长短,严格等分 */
flex: 1;
/*
┌──────────────────────────────────────┐
│ ┌────────────┐ ┌────────────┐        │
│ │     1       │ │     2       │       │  ← 等宽
│ └────────────┘ └────────────┘        │
└──────────────────────────────────────┘
*/

/* flex: auto = flex-grow:1, flex-shrink:1, flex-basis:auto */
/* 先按内容宽度,再分配剩余 → 内容长的更宽 */
flex: auto;
/*
┌──────────────────────────────────────────┐
│ ┌──────┐ ┌───────────────────────────┐   │
│ │  短   │ │  长内容长内容长内容长内容  │   │  ← 不等宽
│ └──────┘ └───────────────────────────┘   │
└──────────────────────────────────────────┘
*/

/* flex: none = flex-grow:0, flex-shrink:0, flex-basis:auto */
/* 不放大不缩小,定死 */
flex: none;

align-self — 某个子项单独对齐 ​

css
.item2 { align-self: flex-end; }
/*
┌──────────────────────────┐
│ ┌──┐        ┌──┐         │
│ │1 │        │3 │         │
│ └──┘        └──┘         │
│                          │
│              ┌──┐         │
│              │2 │  ← 独自沉底
│              └──┘         │
└──────────────────────────┘
*/

经典 Flex 布局速查 ​

水平垂直居中(单个子项) ​

css
.parent {
  display: flex;
  justify-content: center;
  align-items: center;
}
/*
┌─────────────────────────┐
│                         │
│          ┌───┐          │
│          │ ● │          │  ← 正中间
│          └───┘          │
│                         │
└─────────────────────────┘
*/
css
.layout {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.body { display: flex; flex: 1; }
.sidebar { width: 200px; }
.main { flex: 1; }
/*
┌───────────────────────────────────────┐
│              Header                    │
├──────┬──────────────────────┬─────────┤
│ Left │       Main           │  Right  │
│200px │     flex:1           │  200px  │
│      │                      │         │
├──────┴──────────────────────┴─────────┤
│              Footer                    │
└───────────────────────────────────────┘
*/

等分布局(n 列自适应) ​

css
.grid { display: flex; }
.grid > * { flex: 1; }
/*
┌──────┬──────┬──────┬──────┐
│  1   │  2   │  3   │  4   │  ← 严格等宽
└──────┴──────┴──────┴──────┘
*/

一固定一自适应(左侧固定,右侧撑满) ​

css
.sidebar { width: 200px; flex-shrink: 0; }
.content { flex: 1; }
/*
┌───────────┬──────────────────────────┐
│ Sidebar   │  Content(剩余宽度)       │
│ 200px     │  flex: 1                 │
│ 不缩       │                          │
└───────────┴──────────────────────────┘
*/

Grid 布局 ​

核心概念 ​

┌─────────────────────────────────────────────┐
│            Grid Container                   │
│  列1      列2      列3      列4             │
│  ┌────────┬────────┬────────┬────────┐     │
│  │ 单元格  │ 单元格  │ 单元格  │ 单元格  │ ← 行1
│  ├────────┼────────┼────────┼────────┤     │
│  │ 单元格  │ 单元格  │ 单元格  │ 单元格  │ ← 行2
│  ├────────┼────────┼────────┼────────┤     │
│  │ 单元格  │ 单元格  │ 单元格  │ 单元格  │ ← 行3
│  └────────┴────────┴────────┴────────┘     │
│  ↑网格线1  ↑网格线2  ↑网格线3  ↑网格线4   │
└─────────────────────────────────────────────┘

核心术语:
  网格线(grid line)= 行列分界线,从 1 开始编号
  网格轨道(grid track)= 两平行线之间的空间(行 / 列)
  单元格(grid cell)= 行列交叉的最小单位
  网格区域(grid area)= 多个单元格组成的矩形

容器属性 ​

grid-template-columns / grid-template-rows — 定义行列 ​

css
/* 三列:宽 200px | 1/3 剩余 | 1/3 剩余 */
grid-template-columns: 200px 1fr 1fr;
/*
┌──────┬──────────┬──────────┐
│200px │   1fr    │   1fr    │
└──────┴──────────┴──────────┘
*/

/* repeat:3 等分 */
grid-template-columns: repeat(3, 1fr);
/*
┌────────┬────────┬────────┐
│  1fr   │  1fr   │  1fr   │
└────────┴────────┴────────┘
*/

/* repeat + auto-fill:自动填充,能放几个放几个 */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
/*
容器 900px → 4 列各 225px
┌───────┬───────┬───────┬───────┐
│ 225   │ 225   │ 225   │ 225   │
└───────┴───────┴───────┴───────┘

容器 500px → 2 列各 250px
┌───────────┬───────────┐
│   250     │   250     │
└───────────┴───────────┘
*/

/* minmax:设定最小/最大范围 */
grid-template-rows: minmax(100px, auto);
/* 最少 100px,内容多了自动撑高 */

/* 指定行 + auto 隐式行 */
grid-template-rows: 80px 1fr 60px;
/* 第 1 行 80px,第 2 行撑满,第 3 行 60px */
/*
┌─────────────────────────┐
│  80px(header)          │
├─────────────────────────┤
│                         │
│  1fr(content 撑满)      │
│                         │
├─────────────────────────┤
│  60px(footer)          │
└─────────────────────────┘
*/

gap — 行列间距 ​

css
gap: 20px;  /* 行列间距都是 20px */

/* 分开写 */
row-gap: 20px;     /* 行间距 */
column-gap: 10px;  /* 列间距 */

/*
┌──────┐  ← 20px → ┌──────┐
│      │            │      │
│  A   │            │  B   │
│      │            │      │
└──────┘            └──────┘
   ↕ 20px
┌──────┐            ┌──────┐
│      │            │      │
│  C   │            │  D   │
│      │            │      │
└──────┘            └──────┘
*/

grid-template-areas — 可视化区域布局(最强功能) ​

css
.container {
  display: grid;
  grid-template-columns: 200px 1fr 1fr;
  grid-template-rows: 60px 1fr 60px;
  grid-template-areas:
    "header  header  header"
    "sidebar main    main"
    "footer  footer  footer";
}
.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

/*
┌─────────────────────────────────────────┐
│              header                      │
├──────────┬──────────────────────────────┤
│ sidebar  │          main                │
│ 200px    │                              │
├──────────┴──────────────────────────────┤
│              footer                      │
└─────────────────────────────────────────┘
*/

⚠️ 区域名不能是数字开头,区域必须形成矩形(不能是 L 形、T 形)。


justify-items / align-items — 单元格内容对齐 ​

css
/* 默认 stretch:拉伸填满单元格 */
justify-items: stretch;
align-items: stretch;

/*
┌────────────┬────────────┐
│  ┌──────┐  │  ┌──────┐  │
│  │ 撑满  │  │  │ 撑满  │  │
│  │ 撑满  │  │  │ 撑满  │  │
│  └──────┘  │  └──────┘  │
└────────────┴────────────┘
*/

/* start:贴左上角 */
justify-items: start;
align-items: start;
/*
┌────────────┬────────────┐
│  ┌──┐      │  ┌──┐      │
│  │  │      │  │  │      │
│  └──┘      │  └──┘      │
│            │            │
│            │            │
└────────────┴────────────┘
*/

/* center:居中 */
justify-items: center;
align-items: center;
place-items: center;  /* 简写 */
/*
┌────────────┬────────────┐
│            │            │
│    ┌──┐    │    ┌──┐    │
│    │  │    │    │  │    │
│    └──┘    │    └──┘    │
│            │            │
└────────────┴────────────┘
*/

grid-auto-flow — 自动排列方向 ​

css
/* row(默认):按行排列 */
grid-auto-flow: row;
/*
放入顺序:1→2→3→4→5→6
┌────┬────┬────┐
│ 1  │ 2  │ 3  │
├────┼────┼────┤
│ 4  │ 5  │ 6  │
└────┴────┴────┘
*/

/* column:按列排列 */
grid-auto-flow: column;
/*
┌────┬────┬────┐
│ 1  │ 3  │ 5  │
├────┼────┼────┤
│ 2  │ 4  │ 6  │
└────┴────┴────┘
*/

/* dense:自动填补空白 */
grid-auto-flow: dense;
/*
子项 1 跨 2 列,2、3 较小 → 自动填空
┌──────────┬────┬────┐
│    1     │ 2  │ 3  │
├────┬─────┼────┴────┤
│ 4  │  5  │   6     │
└────┴─────┴─────────┘
*/

子项属性 ​

grid-column / grid-row — 基于网格线定位 ​

网格线编号:
  1         2         3         4
  │         │         │         │
  ├─────────┼─────────┼─────────┤ ← 行 1
  │         │         │         │
  ├─────────┼─────────┼─────────┤ ← 行 2
  │         │         │         │
  ├─────────┼─────────┼─────────┤ ← 行 3
  │         │         │         │
  └─────────┴─────────┴─────────┘
css
/* 占第 1 到第 3 列 */
grid-column: 1 / 3;   /* 等价 grid-column-start:1; grid-column-end:3 */
/*
┌──────────────────────┬─────┐
│                      │     │
│        1             │  2  │
│                      │     │
├──────────┬───────────┼─────┤
│    3     │    4      │  5  │
└──────────┴───────────┴─────┘
*/

/* 占 2 列(从第 2 条线开始) */
grid-column: 2 / span 2;
/*
┌─────┬──────────────────────┐
│     │                      │
│  1  │          2           │
│     │                      │
├─────┼──────────┬───────────┤
│  3  │    4     │    5      │
└─────┴──────────┴───────────┘
*/

/* 占 2 行(从第 1 条线开始跨 2 行) */
grid-row: 1 / span 2;
/*
┌──────┬─────┬─────┐
│      │     │     │
│  1   │  2  │  3  │
│      │     │     │
│      ├─────┼─────┤
│      │  4  │  5  │
└──────┴─────┴─────┘
*/

Grid 经典布局速查 ​

圣杯布局(grid-template-areas 版) ​

css
.grid {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: 60px 1fr 60px;
  min-height: 100vh;
  grid-template-areas:
    "header  header  header"
    "left    main    right"
    "footer  footer  footer";
}
/*
┌──────────────────────────────────────────┐
│                 header                    │
├──────┬──────────────────────┬────────────┤
│ left │        main          │   right    │
│200px │       1fr            │   200px    │
├──────┴──────────────────────┴────────────┤
│                 footer                    │
└──────────────────────────────────────────┘
*/

瀑布流 / 自适应卡片 ​

css
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
}
/*
容器 1100px 时 → 4 列
┌───────┬───────┬───────┬───────┐
│ card  │ card  │ card  │ card  │
├───────┼───────┼───────┼───────┤
│ card  │ card  │ card  │ card  │
└───────┴───────┴───────┴───────┘

容器 800px 时 → 3 列
┌──────────┬──────────┬──────────┐
│   card   │   card   │   card   │
├──────────┼──────────┼──────────┤
│   card   │   card   │   card   │
└──────────┴──────────┴──────────┘
*/

不规则布局(不同子项占不同格数) ​

css
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.item1 { grid-column: span 2; grid-row: span 2; }  /* 占 2×2 */
.item6 { grid-column: span 2; }                     /* 占 2×1 */

/*
┌──────────┬──────────┬─────┬─────┐
│          │          │  2  │  3  │
│    1     │    1     ├─────┼─────┤
│          │          │  4  │  5  │
├──────────┼──────────┼─────┼─────┤
│    6     │    6     │  7  │  8  │
└──────────┴──────────┴─────┴─────┘
*/

Flex vs Grid:如何选择 ​

场景用 Flex用 Grid
一维排列(导航、列表、工具栏)✅也可以,但杀鸡用牛刀
二维布局(页面整体、仪表盘)❌ 需要嵌套✅
内容驱动(子项大小决定布局)✅ flex: auto❌ 轨道是容器定义的
布局驱动(容器决定子项位置)❌✅ grid-template-areas
等分 n 列flex: 1repeat(n, 1fr)
自适应卡片需要 flex-wrapauto-fill + minmax 更优雅
水平垂直居中✅ 两行搞定place-items: center 一行搞定
不规则布局❌✅ grid-column/row: span

面试话术:

Flex 是一维弹性布局,擅长在一条轴线上分配空间,适合组件级别的排列;Grid 是二维网格布局,擅长同时控制行和列,适合页面级别的整体布局。现代项目中通常 Grid 做页面骨架,Flex 做组件内部,两者配合使用。

基于 Vue 2.7 & Vue 3.4 源码