Flexbox 布局
为什么 Flexbox 是现代布局的基石
Flexbox(Flexible Box Layout)是 CSS 中第一个真正为一维布局设计的模型。在 Flexbox 之前,开发者依赖 float、inline-block、table 等 hack 来实现水平排列和垂直居中。Flexbox 用一套声明式的 API 优雅地解决了这些问题。
尽管 Grid 布局在二维场景中更强大,Flexbox 仍然是组件级布局的首选——导航栏、按钮组、卡片内部结构、工具栏等场景中,Flexbox 比 Grid 更简洁直观。
面试定位:Flexbox 是前端面试的必考题。面试官通过 flex 简写的默认值、flex-grow 的空间分配算法、shrink 的收缩比例计算等问题,判断候选人是否真正理解 Flexbox 的内部机制。
核心概念:轴与方向
主轴(Main Axis)与交叉轴(Cross Axis)
Flexbox 的一切布局行为都围绕两条轴展开:
flex-direction: row(默认)
主轴 (Main Axis) →
┌─────────────────────────────────────┐
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ ↕ 交叉轴
│ │ A │ │ B │ │ C │ │ D │ │ (Cross Axis)
│ └────┘ └────┘ └────┘ └────┘ │
└─────────────────────────────────────┘
flex-direction: column
交叉轴 (Cross Axis) →
┌──────────┐
│ ┌────┐ │ ↕ 主轴
│ │ A │ │ (Main Axis)
│ └────┘ │
│ ┌────┐ │
│ │ B │ │
│ └────┘ │
│ ┌────┐ │
│ │ C │ │
│ └────┘ │
└──────────┘
| 属性 | 值 | 主轴方向 |
|---|---|---|
flex-direction |
row(默认) |
水平,从左到右(LTR) |
row-reverse |
水平,从右到左 | |
column |
垂直,从上到下 | |
column-reverse |
垂直,从下到上 |
flex-wrap
.container {
display: flex;
flex-wrap: nowrap; /* 默认:不换行,可能溢出或压缩 */
flex-wrap: wrap; /* 空间不足时换行 */
flex-wrap: wrap-reverse; /* 换行但行顺序反转 */
}
/* 简写 */
.container {
flex-flow: row wrap; /* flex-direction + flex-wrap */
}
Flex 容器属性
justify-content —— 主轴对齐
控制 flex items 在主轴上的分布:
.container {
justify-content: flex-start; /* 起始对齐(默认) */
justify-content: flex-end; /* 末尾对齐 */
justify-content: center; /* 居中 */
justify-content: space-between; /* 两端对齐,元素间等距 */
justify-content: space-around; /* 每个元素两侧等距(元素间距是边缘的 2 倍) */
justify-content: space-evenly; /* 所有间距完全相等 */
}
space-between: |A B C D|
space-around: | A B C D | (两侧间距 = 元素间距 / 2)
space-evenly: | A B C D | (所有间距相等)
align-items —— 交叉轴对齐
控制 flex items 在交叉轴上的对齐:
.container {
align-items: stretch; /* 拉伸填满(默认) */
align-items: flex-start; /* 顶部对齐 */
align-items: flex-end; /* 底部对齐 */
align-items: center; /* 垂直居中 */
align-items: baseline; /* 文本基线对齐 */
}
align-content —— 多行交叉轴对齐
只在 flex-wrap: wrap 且有多行时生效:
.container {
flex-wrap: wrap;
align-content: flex-start; /* 所有行靠顶 */
align-content: center; /* 所有行居中 */
align-content: space-between; /* 行间等距 */
align-content: stretch; /* 行拉伸填满(默认) */
}
gap —— 间距
.container {
display: flex;
gap: 16px; /* 行间距和列间距相同 */
gap: 16px 24px; /* row-gap column-gap */
row-gap: 16px; /* 单独设置 */
column-gap: 24px;
}
与 margin 的区别:gap 只在元素之间生效,不会在容器边缘产生额外间距。这消除了传统 margin 方案中"最后一个元素的多余 margin"问题。
Flex 项目属性
flex 简写 —— 三值含义
flex 是 flex-grow、flex-shrink、flex-basis 的简写:
.item {
flex: 1; /* flex: 1 1 0% —— 等比填充 */
flex: auto; /* flex: 1 1 auto —— 基于内容的弹性填充 */
flex: none; /* flex: 0 0 auto —— 不伸不缩 */
flex: 0 1 auto; /* 默认值 —— 不增长,可收缩,基于内容 */
}
关键区别:flex: 1 与 flex: auto 的不同
/* flex: 1 → flex-basis: 0% */
/* 所有项目从 0 开始平分空间,忽略内容宽度 */
.item-equal { flex: 1; }
/* flex: auto → flex-basis: auto */
/* 先按内容分配基础空间,再平分剩余空间 */
.item-auto { flex: auto; }
flex-grow 空间分配算法
.container { width: 600px; display: flex; }
.a { flex: 2; width: 100px; } /* flex-basis: 0%,忽略 width */
.b { flex: 1; width: 100px; }
.c { flex: 1; width: 100px; }
/*
* flex-basis 都是 0%,所以剩余空间 = 600px
* grow 比例:2 : 1 : 1
* A = 600 * 2/4 = 300px
* B = 600 * 1/4 = 150px
* C = 600 * 1/4 = 150px
*/
flex-shrink 收缩算法
收缩算法比增长复杂——它按 flex-shrink × flex-basis 的加权比例分配:
.container { width: 400px; display: flex; }
.a { flex: 0 1 300px; } /* flex-shrink: 1, basis: 300px */
.b { flex: 0 2 200px; } /* flex-shrink: 2, basis: 200px */
/*
* 总 basis = 300 + 200 = 500px
* 溢出量 = 500 - 400 = 100px
* 加权总和 = 1*300 + 2*200 = 700
* A 收缩 = 100 * (1*300/700) ≈ 42.86px → A = 257.14px
* B 收缩 = 100 * (2*200/700) ≈ 57.14px → B = 142.86px
*/
flex-basis vs width
flex-basis |
width |
实际行为 |
|---|---|---|
auto(默认) |
有值 | 使用 width 作为基础尺寸 |
auto |
无值 | 使用内容尺寸 |
| 具体值 | 有值 | flex-basis 覆盖 width |
0% |
有值 | 忽略 width,从 0 开始分配 |
align-self —— 单项对齐
.item {
align-self: auto; /* 继承容器的 align-items(默认) */
align-self: flex-start;
align-self: flex-end;
align-self: center;
align-self: stretch;
}
order —— 视觉顺序
.item-a { order: 2; } /* 默认 order: 0 */
.item-b { order: -1; } /* 排在最前 */
.item-c { order: 1; }
/* 渲染顺序:B → C → A */
无障碍警告:order 仅改变视觉顺序,不改变 DOM 顺序和 Tab 键序。屏幕阅读器仍按 DOM 顺序读取。视觉顺序与 DOM 顺序不一致会导致键盘导航混乱。
常见布局模式
水平垂直居中(终极方案)
.center {
display: flex;
justify-content: center;
align-items: center;
}
/* 或者使用 margin: auto(Flex 专属能力) */
.container { display: flex; }
.child { margin: auto; } /* 自动吸收所有剩余空间 */
粘性底部(Sticky Footer)
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1; /* 主内容撑满剩余空间 */
}
footer {
/* 自动贴底 */
}
导航栏:左侧 logo + 右侧操作区
.navbar {
display: flex;
align-items: center;
gap: 1rem;
}
.navbar .logo { margin-right: auto; } /* 推开右侧内容 */
/* 或者 */
.navbar .actions { margin-left: auto; }
等宽列(不考虑内容差异)
.columns {
display: flex;
gap: 1rem;
}
.columns > * {
flex: 1; /* flex-basis: 0%,平分空间 */
min-width: 0; /* 阻止内容撑破——关键! */
}
min-width: 0 的必要性:Flex items 的默认 min-width 是 auto(即内容最小宽度),会阻止项目收缩到内容宽度以下。设置 min-width: 0 允许项目被压缩。
响应式卡片网格
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card {
flex: 1 1 300px; /* 最小 300px,可增长填满 */
min-width: 0;
}
Flexbox 的边界与陷阱
陷阱一:文本溢出问题
/* 问题:长文本撑破 Flex 布局 */
.item {
flex: 1;
/* 长文本不会自动省略 */
}
/* 解决方案 */
.item {
flex: 1;
min-width: 0; /* 允许收缩到 0 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
陷阱二:图片不受 flex-shrink 控制
/* 问题:图片保持原始尺寸 */
img {
/* 默认 min-width: auto 阻止收缩 */
}
/* 解决方案 */
img {
min-width: 0;
/* 或 */
max-width: 100%;
}
陷阱三:margin: auto 在 Flex 中的特殊行为
在 Flex 容器中,margin: auto 会吸收所有剩余空间,而不仅仅是水平居中:
.flex-container { display: flex; }
/* 完全居中 */
.child { margin: auto; }
/* margin-left: auto → 推到右边 */
.child { margin-left: auto; }
/* 注意:margin: auto 会使 justify-content 失效 */
Flexbox vs Grid 的选择
| 场景 | 推荐 | 原因 |
|---|---|---|
| 一维排列(行或列) | Flexbox | 设计初衷 |
| 二维网格布局 | Grid | Grid 的二维控制更强 |
| 内容驱动的动态布局 | Flexbox | 项目尺寸由内容决定 |
| 布局驱动的固定结构 | Grid | 先定义网格,再放置内容 |
| 组件内部布局 | Flexbox | 通常是一维的 |
| 页面级布局 | Grid | 通常是二维的 |
面试高频问题
Q: flex: 1 和 flex: auto 有什么区别?
回答要点:flex: 1 等价于 flex: 1 1 0%,basis 为 0,所有项目从零开始平分容器空间,忽略内容宽度。flex: auto 等价于 flex: 1 1 auto,basis 为 auto,先按内容分配基础空间,再平分剩余空间。所以 flex: 1 产生等宽列,flex: auto 产生内容感知的弹性列。
Q: flex-shrink 的收缩是如何计算的?
回答要点:收缩不是简单按 shrink 比例分配,而是按 flex-shrink × flex-basis 的加权比例。basis 越大的项目承担更多收缩量。这个设计确保了收缩的"公平性"——大元素收缩更多,小元素收缩更少。
Q: 为什么 Flex 子项需要 min-width: 0?
回答要点:Flex items 的默认 min-width 是 auto,等于内容的最小宽度。这会阻止项目收缩到内容宽度以下,导致溢出。设置 min-width: 0 覆盖这个默认值,允许项目真正按 flex-shrink 收缩。这在文本省略、等宽列等场景中是必须的。