CSS 教程 / Flexbox 对齐、换行、gap、grow 与 shrink
Flexbox 结合了对齐、换行和弹性尺寸,以便将项目放入可用空间。justify-content 在主轴上工作,align-items 在交叉轴上工作,align-content 在有交叉轴空间可分配时分配多行。
将对齐属性与其轴匹配
| 属性 | 适用对象 | 需检查的条件 |
|---|---|---|
| justify-content | 沿容器主轴分布项目 | 设置项目大小后剩余的空间 |
| align-items | 每个项目的交叉轴对齐 | 项目交叉轴大小和行高 |
| align-self | 特定项目的交叉轴对齐 | 单独更改容器默认对齐方式 |
| align-content | 多行的对齐方式 | 可换行容器及交叉轴剩余空间 |
| gap | 相邻项目/行之间的间距 | 通过对齐添加的空白和分隔 |
在本例的水平书写模式下,row 容器的 justify-content 控制水平方向,而 align-items 主要控制垂直方向。如果将 flex-direction 改为 column,主轴将变为垂直方向,这两个属性的物理方向也会随之改变。特别是,容器高度中必须留有空间来比较列的 justify-content。如果高度正好适应内容,即使你将其更改为 center 或 space-between,也几乎没有空间可以移动。轴的方向和 Flex 容器的起始设置可以在 Flexbox 基础:容器、项目和轴 中查看。
justify-content: center 会将一组项目置于中心位置,而 space-between 会将第一个和最后一个项目放在两端,并在它们之间分配空间。gap 是指定的间隙,而 space-between 分配剩余空间。当一起使用时,实际间隙可能大于 gap。如果某个项目的 flex-grow 占用了所有剩余空间,或主轴方向的自动外边距占用了空间,justify-content 的变化可能不可见。
align-items: center 将不同高度的项目在一行内居中对齐。align-content: center 在交叉轴上居中所有行。你不应该将二者视为相同的功能。align-content 对单行且为 nowrap 的容器无效,即使在 wrap 容器中,如果行占据的空间和容器高度相同,也没有比较的余地。MDN:Flexbox 对齐及其适用条件。
比较项目对齐和行对齐
将以下代码保存为 flex-align.html 并打开。第一部分有三个高度不同的项目。第二部分在宽度足够时将四个项目排成两行,并设置 280px 的容器高度,为整组行的移动保留空间。以 UTF-8 编码保存文件,再用浏览器打开。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>flex-align.html</title>
<style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.toolbar { display: flex; gap: 12px; justify-content: space-between; align-items: center;
width: 100%; max-width: 640px; min-height: 120px; border: 2px solid #789; padding: 12px; }
.toolbar > div { padding: 8px; background: #dceefa; }
.lines { display: flex; flex-wrap: wrap; gap: 12px; width: min(100%, 360px); height: 280px;
align-items: center; align-content: flex-start; border: 2px solid #789; padding: 12px; }
.lines > div { flex: 0 0 140px; padding: 8px; background: #e8f5de; }
.tall { min-height: 60px; }
</style>
</head>
<body>
<h1>Alignment on one line and multiple lines</h1>
<h2>Toolbar</h2><div class="toolbar"><div>Short item</div><div>Two-line item<br>Second line</div><div>Button position</div></div>
<h2>Line group</h2><div class="lines"><div>1</div><div class="tall">2<br>Second line</div><div>3</div><div class="tall">4<br>Second line</div></div>
</body>
</html>
- 将 .toolbar 的 justify-content 改为 center,以查看三个项目是否集中在中间。我们还比较 flex-start 并将其恢复为 space-between。项目大小和间距保持不变。
- 将 .toolbar 的 align-items 改为 flex-start,以确保它对齐到顶部。如果改为 stretch,具有自动交叉轴尺寸的项目会增大。比较后,将其恢复为 center。
- 仅将 .lines 的 align-content 改为 center。两行内容作为一个整体向下移动,同时保持每行中各项的相对关系。如果切回 flex-start 并仅将 align-items 改为 flex-start,每行中短项目的位置将会改变。
- 如果你仅将 .lines 的 flex-wrap 改为 nowrap,所有内容都会尝试保持在一行中,而此例使用 flex: 0 0 140px,关闭了项目收缩,会导致水平溢出。在这种状态下,你无法使用 align-content 来对齐多行。确认后,请恢复为 wrap。
理解 grow、shrink 和 basis
flex: 1 1 192px 按顺序是 grow、shrink 和 basis。basis 是用于开始主轴空间分布的标准尺寸,并不是保证最终宽度的值。grow 是剩余空间的放大比例,shrink 是空间不足时的缩小比例。由于在简写中单个数字或 auto 有省略值规则,因此在学习示例中指定了三个值并进行比较。
| 声明 | 当有剩余空间时 | 当空间用尽时 |
|---|---|---|
| flex: 0 1 192px | 保持标准尺寸 | 参与缩小 |
| flex: 1 1 192px | 剩余空间将被分配 | 参与缩小 |
| flex: 0 0 192px | 保持标准尺寸 | 不缩小。如果设置为nowrap,它可能会溢出。 |
例如,如果一个内容宽度为640px的父容器包含三个基本尺寸为100px的项目和两个间隙(每个20px),剩余空间为 640 − 300 − 40 = 300px。如果增长比例为1:2:1,增加的空间将分别是75px、150px和75px,最终宽度将是175px、250px和175px。这是一个不涉及最小内容尺寸或最小/最大约束条件的计算。
缩小量不能只按 flex-shrink 的数值直接分配。需要同时考虑缩减比例和参考尺寸。如果我们需要减少两项的60px,它们的标准尺寸分别是200px和100px,并且两者的收缩系数都是1,那么在没有约束的简单条件下,我们会减少40px和20px。如果存在内边距、边框、最小内容尺寸等因素,结果可能会有所不同,因此你应该实际检查应用的值。MDN:flex 解释了简写形式和尺寸条件。
分配空间并换行卡片
将以下代码保存为 flex-size.html。计算区域通过创建最大宽度为 640px 而不添加额外的内边距或边框来对应上述计算。卡片列表使用 192px 的宽度作为换行的标准,但会填充剩余空间。如果带有 list-style: none 的列表位于 nav 之外,可能在 Safari 的辅助功能树中无法被识别为列表。在示例中,ul 指定 role="list" 以指定列表角色。移除项目符号和间距时的辅助功能条件也在 CSS 段落和列表样式 中进行了描述。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>flex-size.html</title>
<style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.calc { display: flex; gap: 20px; width: min(100%, 640px); background: #eef2f6; }
.calc > div { flex: 1 1 100px; min-width: 0; height: 80px; background: #dceefa; }
.calc > div:nth-child(2) { flex-grow: 2; }
.cards { display: flex; flex-wrap: wrap; gap: 16px; list-style: none; padding: 0; }
.cards > li { flex: 1 1 192px; min-width: 0; padding: 16px; background: #e8f5de; overflow-wrap: anywhere; }
</style>
</head>
<body>
<h1>Flex values and space distribution</h1><div class="calc"><div>grow 1</div><div>grow 2</div><div>grow 1</div></div>
<h2>Wrapping card</h2><ul class="cards" role="list"><li>First card</li><li>Second card</li><li>Third card</li><li>Fourth card</li></ul>
</body>
</html>
- 将浏览器扩展到 .calc 宽度为 640px,然后使用开发者工具检查三个项目的宽度。它们将分别是 175px·250px·175px。如果仅将第二个项目的 grow 改为 1,三个项目将变为每个 200px。比较后,将其恢复为 2。
- 只需将 .cards > li 中的 grow 改为 0,以确保最后一行的卡片不会填充多余的宽度。如果将其恢复为 1,最后一行也会获得空间。
- 缩小屏幕以查看一行中是否能放下更少的卡片。wrap 决定放置哪几行,每行分别分配剩余空间。最后一行和前一行的卡片宽度不一定相同。
- 要看到单行溢出而不是换行,请将 flex-wrap 替换为 nowrap,并将 .cards > li 的 flex 设置为 0 0 192px。检查完这两个条件后,恢复原来的 wrap 和 1 1 192px。
检查自动最小尺寸和长文本
Flex 项的自动最小宽度可能会阻止包含长地址或标识符的区域被缩短。CSS 溢出、长文本换行和省略号 进一步说明溢出处理;在这里,min-width: 0 允许 Flex 项收缩,而 overflow-wrap: anywhere 允许字符串本身换行。缩小一个盒子不会自动截断其中的文本,因此需要区分这两类问题。
将下面的代码保存为 flex-long-text.html。示例使用没有空格的长标识符,并非真实地址;保存为 UTF-8 后在浏览器中打开。即使屏幕变窄,引导区域旁边的文本也会被分成多行阅读。换行规则详见 MDN:overflow-wrap 。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>flex-long-text.html</title>
<style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.notice { display: flex; gap: 12px; max-width: 600px; border: 2px solid #789; padding: 12px; }
.icon { flex: 0 0 48px; background: #ffe4b5; }
.text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; background: #dceefa; }
.text p { margin: 0; }
</style>
</head>
<body>
<h1>Long strings and minimum sizes</h1><div class="notice"><div class="icon">Notice</div><div class="text"><p>ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</p></div></div>
</body>
</html>
- 在 .text 中,只需将 overflow-wrap 改为 normal。即使项目宽度减小,字符串也可能会溢出边界。比较后恢复为 anywhere。
- 要单独查看 min-width 的影响,请在保持 overflow-wrap: normal 的同时移除 min-width: 0。确定长字符串的最小尺寸如何影响项目布局后,将两个声明恢复为原始值。
如果无法对齐,首先检查父元素的轴和剩余空间。如果尺寸没有减小,则检查子项的最小尺寸和内部字符串。如果你对整个父元素设置 overflow: hidden 来隐藏问题,可能会丢失内容或焦点指示。如果将布局尺寸和内容显示分开解决,更容易判断结果。




