Flexbox 布局

系统掌握 Flexbox 一维布局模型的核心算法:主轴/交叉轴、flex 简写的三值含义、空间分配机制、对齐体系,以及常见布局模式的最佳实践。

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 简写 —— 三值含义

flexflex-growflex-shrinkflex-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: 1flex: 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-widthauto(即内容最小宽度),会阻止项目收缩到内容宽度以下。设置 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-widthauto,等于内容的最小宽度。这会阻止项目收缩到内容宽度以下,导致溢出。设置 min-width: 0 覆盖这个默认值,允许项目真正按 flex-shrink 收缩。这在文本省略、等宽列等场景中是必须的。


延展阅读