Flex弹性布局
为什么 Flex 布局值得单独掌握
传统 CSS 布局依赖 float、position 和 display: inline-block,这些方案在处理"一行内多元素等分"、"垂直居中"、"剩余空间自动分配"等需求时,写法繁琐且容易出现意外行为。Flex 布局(Flexible Box Layout)正是为了解决这些问题而设计的一维布局模型。
面试中,Flex 是 CSS 布局的必考项。不仅要知道每个属性的作用,还要能说清楚属性之间的联动关系、flex 简写的展开值,以及实际项目中如何选择 Flex 而非 Grid。
核心概念:轴与容器
Flex 布局围绕两根轴展开:
- 主轴(main axis):项目排列的方向,由
flex-direction决定。 - 交叉轴(cross axis):垂直于主轴的方向。
一个 Flex 布局包含两类角色:
- Flex 容器(Flex Container):设置了
display: flex或display: inline-flex的父元素。 - Flex 项目(Flex Item):容器的直接子元素,自动成为 Flex 项目。
.container {
display: flex; /* 块级 Flex 容器 */
}
.container-inline {
display: inline-flex; /* 行内 Flex 容器 */
}
display: flex 使容器本身表现为块级元素;display: inline-flex 使容器本身表现为行内元素。两者对内部项目的布局行为没有区别,区别仅在于容器自身在外部文档流中的表现。
容器属性
以下六个属性设置在 Flex 容器上,控制项目的排列方向、换行方式和对齐策略。
flex-direction:主轴方向
.container {
flex-direction: row; /* 默认值,水平从左到右 */
flex-direction: row-reverse; /* 水平从右到左 */
flex-direction: column; /* 垂直从上到下 */
flex-direction: column-reverse; /* 垂直从下到上 */
}
改变 flex-direction 会同时改变主轴和交叉轴的方向。当设为 column 时,justify-content 控制的是垂直方向,align-items 控制的是水平方向——这是面试中非常容易踩的坑。
flex-wrap:是否换行
.container {
flex-wrap: nowrap; /* 默认值,不换行,项目可能被压缩 */
flex-wrap: wrap; /* 超出容器宽度时换行 */
flex-wrap: wrap-reverse; /* 换行,但行的排列方向反转 */
}
默认 nowrap 下,所有项目会挤在一行。如果项目总宽度超过容器,Flex 会按照各项目的 flex-shrink 值进行压缩。只有设置了 flex-wrap: wrap,项目才会折到下一行。
justify-content:主轴对齐
控制项目在主轴上的分布方式。
.container {
justify-content: flex-start; /* 默认值,靠主轴起点 */
justify-content: flex-end; /* 靠主轴终点 */
justify-content: center; /* 居中 */
justify-content: space-between; /* 两端对齐,项目间等距 */
justify-content: space-around; /* 每个项目两侧等距,首尾有间距 */
justify-content: space-evenly; /* 所有间距完全相等 */
}
space-between 与 space-around 的区别是高频考点:
space-between:首个项目贴起点,末尾项目贴终点,中间项目等距分布。space-around:每个项目左右各有相等的间距,导致首尾项目与容器边缘的间距是项目之间间距的一半。space-evenly:所有间距(包括首尾与容器边缘的间距)完全相等。
align-items:交叉轴对齐(单行)
控制项目在交叉轴上的对齐方式。
.container {
align-items: stretch; /* 默认值,项目拉伸填满交叉轴 */
align-items: flex-start; /* 靠交叉轴起点 */
align-items: flex-end; /* 靠交叉轴终点 */
align-items: center; /* 交叉轴居中 */
align-items: baseline; /* 按文本基线对齐 */
}
默认值 stretch 意味着:如果项目没有设置固定高度(flex-direction: row 时)或固定宽度(flex-direction: column 时),项目会自动拉伸到与容器交叉轴方向等高/等宽。
baseline 在多个项目字号不同时特别有用,它让所有项目的第一行文本基线对齐,视觉上更整齐。
align-content:交叉轴对齐(多行)
只在 flex-wrap: wrap 生效,且容器内有多行时才有意义。控制多行之间在交叉轴上的分布方式。
.container {
flex-wrap: wrap;
align-content: stretch; /* 默认值,各行拉伸填满 */
align-content: flex-start; /* 多行靠交叉轴起点 */
align-content: flex-end; /* 多行靠交叉轴终点 */
align-content: center; /* 多行整体居中 */
align-content: space-between; /* 行间等距,首尾行贴边 */
align-content: space-around; /* 每行上下等距 */
}
面试追问:align-items 和 align-content 的区别是什么?
align-items作用于每一行内部的项目对齐。align-content作用于多行之间的整体分布。- 只有一行时,
align-content无效。
项目属性
以下属性设置在 Flex 项目(子元素)上,控制单个项目的伸缩行为和排列顺序。
flex-grow:放大比例
.item {
flex-grow: 0; /* 默认值,不放大 */
}
当容器主轴方向有剩余空间时,flex-grow 决定每个项目分到多少剩余空间。计算方式:
项目额外获得的空间 = 剩余空间 × (该项目的 flex-grow / 所有项目 flex-grow 之和)
.container {
display: flex;
width: 600px;
}
.item-a { width: 100px; flex-grow: 1; }
.item-b { width: 100px; flex-grow: 2; }
.item-c { width: 100px; flex-grow: 1; }
剩余空间 = 600 - 100 - 100 - 100 = 300px。item-a 获得 300 × 1/4 = 75px,item-b 获得 300 × 2/4 = 150px,item-c 获得 75px。最终宽度分别是 175px、250px、175px。
flex-shrink:缩小比例
.item {
flex-shrink: 1; /* 默认值,允许缩小 */
}
当容器空间不足(flex-wrap: nowrap 时),flex-shrink 决定各项目如何按比例缩小。计算时需要同时考虑 flex-shrink 值和项目自身的基础尺寸:
项目缩小量 = 溢出量 × (该项目的 flex-shrink × flex-basis) / Σ(各项目的 flex-shrink × flex-basis)
设置 flex-shrink: 0 可以阻止某个项目被压缩,这在"固定宽度侧边栏 + 自适应内容区"的布局中非常常用。
flex-basis:基础尺寸
.item {
flex-basis: auto; /* 默认值,取 width/height 或内容尺寸 */
flex-basis: 200px;
flex-basis: 30%;
flex-basis: 0;
}
flex-basis 定义了项目在分配剩余空间之前的初始大小。它和 width 的关系:
- 当
flex-basis为auto时,项目使用自身的width(或height,取决于主轴方向)。 - 当
flex-basis设为具体值时,优先级高于width。 flex-basis: 0表示项目的初始尺寸为 0,所有空间都参与flex-grow分配。
order:排列顺序
.item {
order: 0; /* 默认值,按 DOM 顺序排列 */
}
order 值越小,排列越靠前。可以为负值。相同 order 的项目按 DOM 顺序排列。
实际项目中,order 偶尔用于"视觉顺序与 DOM 顺序不一致"的场景,比如移动端将侧边栏排到内容下方。但要注意,order 只改变视觉顺序,不改变 Tab 键导航和屏幕阅读器的顺序,可能影响无障碍体验。
align-self:单项目对齐
.item {
align-self: auto; /* 默认值,继承容器的 align-items */
align-self: flex-start;
align-self: flex-end;
align-self: center;
align-self: stretch;
align-self: baseline;
}
align-self 允许单个项目覆盖容器的 align-items 设置。典型场景:一排按钮整体顶部对齐,但其中某个按钮需要底部对齐。
flex 简写属性
flex 是 flex-grow、flex-shrink、flex-basis 的简写,面试中被追问的频率极高。
.item {
flex: <flex-grow> <flex-shrink> <flex-basis>;
}
常见简写值及其展开
| 简写 | 展开值 | 含义 |
|---|---|---|
flex: 1 | flex: 1 1 0% | 等比例分配所有空间 |
flex: auto | flex: 1 1 auto | 基于内容尺寸,可伸可缩 |
flex: none | flex: 0 0 auto | 完全不伸缩,保持内容尺寸 |
flex: 0 | flex: 0 1 0% | 不放大,可缩小,基础尺寸为 0 |
flex: 0 auto | flex: 0 1 auto | 等同于 flex: initial(默认值) |
flex: 1 与 flex: auto 的区别
这是面试中最常见的追问之一。
flex: 1展开为flex: 1 1 0%,flex-basis为 0,意味着所有项目忽略自身内容宽度,完全按flex-grow比例等分容器空间。flex: auto展开为flex: 1 1 auto,flex-basis为 auto,意味着先按各项目内容宽度占位,剩余空间再按比例分配。
/* 三个项目内容分别为 "短"、"这是一段较长文本"、"中等" */
/* flex: 1 → 三个项目等宽 */
.item { flex: 1; }
/* flex: auto → 内容多的项目更宽 */
.item { flex: auto; }
等分布局用 flex: 1,按内容比例分配用 flex: auto。
常见布局场景实战
等分布局
N 个项目平均分配容器宽度,无论内容多少。
.container {
display: flex;
}
.item {
flex: 1; /* 等同于 flex: 1 1 0% */
}
如果某个项目内容特别长(比如一段不换行的 URL),可能撑破等分。解决方式是加上 min-width: 0 或 overflow: hidden:
.item {
flex: 1;
min-width: 0; /* 允许项目缩小到内容尺寸以下 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
为什么需要 min-width: 0? Flex 项目的默认 min-width 是 auto,浏览器会保证项目至少和内容一样宽。设置为 0 后,项目才能真正缩小到 flex-basis 指定的尺寸以下。
固定 + 自适应布局
左侧固定宽度,右侧自适应填满。
.container {
display: flex;
}
.sidebar {
width: 240px;
flex-shrink: 0; /* 防止侧边栏被压缩 */
}
.content {
flex: 1; /* 占据剩余空间 */
}
flex-shrink: 0 是关键。不加的话,当窗口缩小时侧边栏也会被压缩。
圣杯布局
经典三栏布局:左右侧边栏固定宽度,中间内容区自适应。
.container {
display: flex;
min-height: 100vh;
}
.left-sidebar {
width: 200px;
flex-shrink: 0;
}
.main-content {
flex: 1;
}
.right-sidebar {
width: 180px;
flex-shrink: 0;
}
<div class="container">
<aside class="left-sidebar">左侧栏</aside>
<main class="main-content">内容区</main>
<aside class="right-sidebar">右侧栏</aside>
</div>
相比 float 方案,Flex 实现圣杯布局不需要额外的 clearfix、负边距或 padding 补偿,DOM 顺序与视觉顺序一致,代码更直观。
粘性 Footer
页面内容不足一屏时,Footer 贴在底部;内容超过一屏时,Footer 自然跟在内容后面。
body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
}
header {
/* 固定高度或自适应 */
}
main {
flex: 1; /* 占据所有剩余垂直空间 */
}
footer {
/* 固定高度或自适应 */
}
<body>
<header>顶部</header>
<main>内容区</main>
<footer>底部</footer>
</body>
核心思路:容器设为纵向 Flex(flex-direction: column),内容区设 flex: 1 吃掉所有剩余空间,Footer 自然被推到底部。
垂直水平居中
Flex 实现居中是最简洁的方案。
.container {
display: flex;
justify-content: center; /* 主轴居中 */
align-items: center; /* 交叉轴居中 */
height: 100vh;
}
只需要两行对齐属性,不需要知道子元素的尺寸,不需要 transform hack,也不需要表格布局的 vertical-align。
等高列
多个列内容高度不同,但视觉上需要等高。
.container {
display: flex;
}
.column {
/* 不需要额外设置,align-items 默认 stretch */
background: #f5f5f5;
padding: 16px;
}
Flex 容器的 align-items 默认值就是 stretch,子项目会自动拉伸到最高项目的高度。这是 Flex 的天然行为,传统布局需要用 JS 计算或 table 模拟才能实现。
面试高频追问
Q:Flex 布局和 Grid 布局怎么选?
Flex 是一维布局模型,擅长处理一行或一列的排列。Grid 是二维布局模型,擅长同时控制行和列。经验法则:组件内部用 Flex,页面级别用 Grid。一个导航栏内部按钮的排列用 Flex,整个页面的 header/sidebar/content/footer 划分用 Grid。两者不冲突,经常嵌套使用。
Q:flex-basis 和 width 同时设置,谁生效?
flex-basis 优先级高于 width(主轴为水平方向时)。但 min-width 和 max-width 的优先级高于 flex-basis。优先级链:min-width / max-width > flex-basis > width。
Q:为什么设了 flex: 1 但项目没有等宽?
常见原因有三个:
- 项目内部有不换行的长内容(如长 URL),撑大了最小宽度。解决:加
min-width: 0。 - 项目设置了
padding或border,而没有使用box-sizing: border-box。 - 项目内部有固定宽度的子元素。
Q:flex-wrap: wrap 后如何控制最后一行的对齐?
这是一个经典难题。使用 justify-content: space-between 加上 flex-wrap: wrap 时,如果最后一行项目数不满,项目会散开而非靠左排列。常见解决方案:
- 使用 CSS Gap(
gap属性)替代space-between+ 外边距的组合。 - 用伪元素占位:在容器尾部用
::after添加空项目撑开位置。 - 如果项目数固定,用 Grid 布局更合适。
Q:Flex 项目的 margin: auto 有什么特殊行为?
在 Flex 容器中,margin: auto 会吸收该方向上的所有剩余空间。这比 justify-content 更灵活:
.container {
display: flex;
}
/* 将最后一个按钮推到最右侧 */
.last-button {
margin-left: auto;
}
这个技巧常用于导航栏中"左侧 Logo + 菜单,右侧一个按钮"的布局。
总结
Flex 弹性布局的核心知识可以归纳为五点:
- 两根轴:主轴由
flex-direction定义,交叉轴垂直于主轴,所有对齐属性都围绕这两根轴工作。 - 容器六属性:
flex-direction、flex-wrap、justify-content、align-items、align-content控制整体排列;display: flex开启容器。 - 项目五属性:
flex-grow、flex-shrink、flex-basis控制伸缩;order调序列;align-self覆盖单项对齐。 - flex 简写:
flex: 1等分空间(basis 为 0),flex: auto按内容比例分配(basis 为 auto),flex: none完全固定。 - 实战中的细节:
min-width: 0解决溢出、flex-shrink: 0固定侧栏、margin: auto吸收空间、gap替代间距 hack。
Flex 布局覆盖了绝大多数一维排列需求,掌握属性之间的联动关系和边界条件,面试中才能给出有深度的回答。