Flex弹性布局

17 分钟

为什么 Flex 布局值得单独掌握

传统 CSS 布局依赖 floatpositiondisplay: inline-block,这些方案在处理"一行内多元素等分"、"垂直居中"、"剩余空间自动分配"等需求时,写法繁琐且容易出现意外行为。Flex 布局(Flexible Box Layout)正是为了解决这些问题而设计的一维布局模型。

面试中,Flex 是 CSS 布局的必考项。不仅要知道每个属性的作用,还要能说清楚属性之间的联动关系、flex 简写的展开值,以及实际项目中如何选择 Flex 而非 Grid。

核心概念:轴与容器

Flex 布局围绕两根轴展开:

  • 主轴(main axis):项目排列的方向,由 flex-direction 决定。
  • 交叉轴(cross axis):垂直于主轴的方向。

一个 Flex 布局包含两类角色:

  • Flex 容器(Flex Container):设置了 display: flexdisplay: 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-betweenspace-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-itemsalign-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-basisauto 时,项目使用自身的 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 简写属性

flexflex-growflex-shrinkflex-basis 的简写,面试中被追问的频率极高。

.item {
  flex: <flex-grow> <flex-shrink> <flex-basis>;
}

常见简写值及其展开

简写展开值含义
flex: 1flex: 1 1 0%等比例分配所有空间
flex: autoflex: 1 1 auto基于内容尺寸,可伸可缩
flex: noneflex: 0 0 auto完全不伸缩,保持内容尺寸
flex: 0flex: 0 1 0%不放大,可缩小,基础尺寸为 0
flex: 0 autoflex: 0 1 auto等同于 flex: initial(默认值)

flex: 1flex: auto 的区别

这是面试中最常见的追问之一。

  • flex: 1 展开为 flex: 1 1 0%flex-basis 为 0,意味着所有项目忽略自身内容宽度,完全按 flex-grow 比例等分容器空间。
  • flex: auto 展开为 flex: 1 1 autoflex-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: 0overflow: hidden

.item {
  flex: 1;
  min-width: 0; /* 允许项目缩小到内容尺寸以下 */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

为什么需要 min-width: 0 Flex 项目的默认 min-widthauto,浏览器会保证项目至少和内容一样宽。设置为 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 自然跟在内容后面。

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-basiswidth 同时设置,谁生效?

flex-basis 优先级高于 width(主轴为水平方向时)。但 min-widthmax-width 的优先级高于 flex-basis。优先级链:min-width / max-width > flex-basis > width

Q:为什么设了 flex: 1 但项目没有等宽?

常见原因有三个:

  1. 项目内部有不换行的长内容(如长 URL),撑大了最小宽度。解决:加 min-width: 0
  2. 项目设置了 paddingborder,而没有使用 box-sizing: border-box
  3. 项目内部有固定宽度的子元素。

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 弹性布局的核心知识可以归纳为五点:

  1. 两根轴:主轴由 flex-direction 定义,交叉轴垂直于主轴,所有对齐属性都围绕这两根轴工作。
  2. 容器六属性flex-directionflex-wrapjustify-contentalign-itemsalign-content 控制整体排列;display: flex 开启容器。
  3. 项目五属性flex-growflex-shrinkflex-basis 控制伸缩;order 调序列;align-self 覆盖单项对齐。
  4. flex 简写flex: 1 等分空间(basis 为 0),flex: auto 按内容比例分配(basis 为 auto),flex: none 完全固定。
  5. 实战中的细节min-width: 0 解决溢出、flex-shrink: 0 固定侧栏、margin: auto 吸收空间、gap 替代间距 hack。

Flex 布局覆盖了绝大多数一维排列需求,掌握属性之间的联动关系和边界条件,面试中才能给出有深度的回答。