Flex 布局这个系列写到第三篇,前面已经把容器的 display、flex-direction、flex-wrap 这些基础概念过了一遍。评论区不少读者留言说,最头疼的就是 justify-content、align-items、align-content 这三个属性,看起来都是“对齐”,但每次用都要试半天,有些时候设了还不生效。这篇我打算把这三个容器属性彻底拆开讲清楚,搞清楚它们各自的管辖范围和生效条件,以后写布局基本就是“肌肉记忆”级别,不用再反复查文档。
这篇文章适合刚入门 CSS Flex、被各种属性绕晕的初学者,也适合工作里写过很多页面但没系统梳理过这三个属性的开发者。我会结合代码示例、生活化类比和实际踩过的坑来展开,尽量让大家看完就知道怎么用、用在哪、以及为什么。
1. 先把轴搞明白,再说对齐
1.1 主轴与交叉轴
要彻底理解这三个属性,必须先建立“轴”的概念。Flex 容器内部有两条轴:主轴(main axis)和交叉轴(cross axis)。主轴方向由 flex-direction 决定,默认是 row,也就是从左到右水平排列;改成 column 后,主轴变为从上到下垂直排列。交叉轴永远和主轴垂直,主轴是水平时交叉轴就是竖直,主轴是垂直时交叉轴就是水平。
这个设定像排队买东西:队伍整体延伸的方向是主轴,而每个人站在队伍里的左右位置、前后位置,则由交叉轴来约束。如果管理员让队伍“靠左边站”,那是在调整交叉轴上的位置;如果管理员说“人与人之间拉开点距离”,那操作的是主轴上的分布。想清楚了这两条轴,后面的属性就各归各位了。
还有一个细节:主轴方向不仅受 row 还是 column 影响,还受 row-reverse 和 column-reverse 影响。这个 reverse 后缀会同时翻转移轴的起点和终点,导致 flex-start 的视觉位置发生变化。很多人习惯默认“flex-start 在最左边”,但一旦容器设置了 row-reverse,flex-start 会跑到右边,这个点后面还会遇到。
1.2 三个属性的分工边界
三个容器属性干的事情截然不同,我先把分工说清楚:
- justify-content:控制主轴方向上,多个项目整体如何分布。
- align-items:控制交叉轴方向上,每一行内部项目如何对齐。
- align-content:控制交叉轴方向上,多行项目整体如何分布。
这三个属性的关系可以类比成一个书架:justify-content 决定一排书在书架横向上是靠左、靠右还是分散放;align-items 决定这一排书在每层隔板上是顶到上沿、压到下沿还是居中;align-content 决定放了好几排书时,这些排与排之间如何分配垂直空间。
下面用一张表快速对比:
| 属性 | 作用轴 | 典型生效条件 | 核心作用 |
|---|---|---|---|
| justify-content | 主轴 | 容器有剩余主轴空间 | 多个项目在主轴上的分布 |
| align-items | 交叉轴 | 单行内部 | 同一行项目在交叉轴的对齐 |
| align-content | 交叉轴 | flex-wrap 且产生换行 | 多行在交叉轴的分布 |
很多人把 align-items 和 align-content 当成一回事,其实它们最大的区别在于:align-items 管的是“每一行里项目自己的上下位置”,align-content 管的是“多行之间的间距和整体分布”。前者即使只有一行也生效,后者必须发生换行才有作用。这一点是最常见的误区,后面我也会反复强调。
2. justify-content:主轴上的空间分配
2.1 六个取值逐个看
justify-content 的取值有 flex-start、flex-end、center、space-between、space-around、space-evenly 六个。它们控制的是项目在主轴方向上的分布方式。我用一个最简单的例子来说明,假设容器宽度 600px,里面有 3 个宽度为 100px 的项,剩余空间就是 300px。此时不同取值的效果大致如下:
- flex-start(默认值):3 个项目紧挨着贴在主轴起点,剩余空间全部留在后面。
- flex-end:3 个项目紧挨着贴在主轴终点,剩余空间全部留在前面。
- center:项目整体居中,剩余空间平均分成两份,左右各一份。
- space-between:第一项贴起点,最后一项贴终点,剩余空间平均分配到项目之间的空隙中。
- space-around:每个项目左右两侧分配相等的空间,所以最左和最右项目外侧也有“半个空隙”的间距。
- space-evenly:所有间隔完全相等,包括两端和最中间的间距,视觉效果最均匀。
从实践角度看,space-between 和 space-around 是很容易视觉混淆的一组。记住一个口诀即可:between 是“两端贴边、中间等分”,around 是“每个项目两侧等距”,所以 around 的两端会留出空隙,between 两端是贴齐的。
2.2 这些细节不说容易翻车
第一个容易翻车的点是:子项总宽度超过容器时,justify-content 基本不生效。因为 Flex 项目默认会收缩,shrink 优先于空间分配,项目先把可用的空间占满甚至溢出,没有剩余空间给 justify-content 去分配。如果你发现设了 justify-content 没有任何视觉效果,第一步先检查容器里是否挤满了项目。
第二个细节是 reverse 方向。当 flex-direction 设置为 row-reverse 或 column-reverse 时,主轴起点和终点会对调。这样 flex-start 和 flex-end 的视觉位置也会反转。我在写上下结构的移动端菜单时,曾经用 column-reverse 让“发布按钮”从底部变为顶部,结果忘了 justify-content: flex-start 的方向也跟着变了,调试了很久才发现是方向问题。
第三个要特别提醒:代码里设置 gap 之后,space-between 这类属性的表现会被叠加影响。gap 相当于给项目之间设定了“固定间距”,space-between 则会把剩余空间继续分配到间隙里。最终视觉看到的是 gap 加均分空间后的总间距。想要精确控制间距时,建议只用 gap 或者只用 space-between 其中一个,混用容易让间距不可控。
还有一点是居中溢出问题。当项目总宽度接近甚至超过容器宽度时,justify-content: center 会让左侧的项目溢出到容器左边,而且这种情况下左侧内容没法通过滚动访问到,对可访问性有影响。如果项目可能撑爆容器,center 要谨慎使用,可以考虑换成 flex-start 配合 margin 自动缩进的方式。
3. align-items:交叉轴上的单行对齐
3.1 默认值 stretch 的隐性规则
align-items 控制容器内每一行项目在交叉轴上的对齐方式,可取值有 stretch、flex-start、flex-end、center、baseline。默认值是 stretch,也就是“拉伸填满”。
stretch 这条规则有很强的隐蔽性。很多教育资料只说“默认 stretch 会让项目拉伸到容器高度”,但没说前提条件:项目必须没有显式设置交叉轴尺寸。如果你的项设置了 height: 100px,那么 stretch 就不会去拉伸它,而是保持 100px。同样,如果设置了 align-self 覆盖,也会优先于容器的 align-items。
我实际中经常用 stretch 来做“等高卡片”效果。比如一行三个卡片,内容量不一样,只要不设置高度,align-items 默认是 stretch,三个卡片的高度都会自动撑到统一高度,非常省事。如果哪个卡片设了固定高度,等高效果就破了,排查时优先检查子项的交叉轴尺寸。
3.2 项目内文字、图标的对齐细节
flex-start、flex-end、center 很好理解,分别对应顶部堆齐、底部堆齐和垂直居中。这个是导航栏、按钮组、表单行里最常用的组合。
baseline 则需要单独说一下。baseline 对齐的是“文本基线”,而不是项目本身的中线或底部。举个例子,左侧是个 40px 高、带文字的按钮,右侧是个 24px 高、带小图标的文本块,如果用 center,视觉上两个项目是居中对齐的,但文字底部可能不在一条线上;如果用 baseline,两个项目的首行文字基线会严格对齐,观感上更整齐。
我实际测试下来,baseline 在“图标 + 文本混合行”里非常有用。比如一个消息列表,左边头像下方有小字号说明,右边是标题,如果只靠 align-items: center,标题和说明文字容易错位;改用 baseline 后,标题文字底边会跟说明文字的基线对齐,整体舒服很多。
需要注意的是,baseline 对齐的是内容的基线,如果子项没有文本内容、也没有内联元素,它会退化到类似 flex-start 的底部边距对齐。想要基线对齐,尽量保证子项内部有文字、行内元素或 table 结构。
另外 align-self 是针对单个项目的属性,允许给特定子项覆盖容器的 align-items 值。比如容器设置 alignItems: center,但中间某一个项目想贴底部,就可以给它单独设 align-self: flex-end。这个属性在灵活布局里很有用,但要注意它只影响交叉轴,和主轴无关。
4. align-content:多行容器的“第二主轴上对齐”
4.1 生效条件:flex-wrap 与多行
align-content 可能是这三个属性里最容易被误解的一个。它控制的是交叉轴方向上多行项目整体的分布,取值和 justify-content 很像,也有 flex-start、flex-end、center、space-between、space-around、stretch、space-evenly。
生效条件比较苛刻,必须同时满足两点:容器设置了 flex-wrap: wrap(或 wrap-reverse),并且项目实际发生了换行。如果只有一行,align-content 会被浏览器完全忽略,所以你设了 align-content: center 却发现什么都没变,是很正常的,因为这个属性根本还没到它出场的时候。
很多新手看到 alignItems 和 align-content 都有 center,就在单行容器里用 align-content: center 想实现垂直居中,结果当然失败。单行垂直居中的正解是 align-items: center,而不是 align-content。有一次我在公司培训时专门做过一个现场演示,把同样的代码从 align-items 改成 align-content,视觉效果直接变了,台下好几个人都发出“原来如此”的声音。
4.2 align-content 与 justify-content 的异同
align-content 与 justify-content 的取值几乎一样,视觉逻辑也一致,只不过一个作用在主轴、一个作用在交叉轴上的多行之间。可以说 align-content 就是“交叉轴方向的 justify-content”,只不过它额外多了一个 stretch 默认值,且它不会处理单行情况。
stretch 在 align-content 里的表现是:每一行会被拉伸,把容器交叉轴方向的剩余空间均分给每一行。默认情况下,如果 flex-wrap 还没有多行,align-content 这个默认值也不会被看见。一旦产生多行且容器有剩余空间,stretch 会把多行项目撑满整个高度,这在某些情况下会有点意外:你明明设置了固定的行高模板,最后一行却被拉长了。
如果想让多行项目整体居上、居下或者居中,就显式设置 align-content 的取值。比如做一个照片瀑布流,外层容器高度固定,内层有多行卡片,设置 align-content: flex-start,卡片从顶部依次排列,不拉伸不满铺,视觉上更贴近真实瀑布流。
这里提醒一个经验:多行布局里,align-content 往往和 flex-wrap 密切相关。只要看到 flex-wrap: wrap,就应该想一想,这个容器是不是还需要配置 align-content 来控制行与行的分布。很多默认 stretch 的问题,都是因为没意识到 align-content 的默认值也在起作用。
5. 实战组合:三兄弟如何搭配干活
5.1 典型场景一:导航栏布局
导航栏是这三个属性最经典的应用场景。顶部导航通常左侧放 logo、中间放菜单、右侧放操作按钮,整体要求在水平方向铺开、垂直方向居中对齐。核心代码就是:
.nav { display: flex; justify-content: space-between; align-items: center; }这里 justify-content 管的是“三个区域在容器宽度上的分布”,space-between 让 logo、菜单、按钮两端拉开;align-items 管的是“三个区域在垂直方向上的位置”,center 保证它们在导航栏高度内居中。这两个属性组合起来,一个常见顶部导航就出来了。
如果你有居中的菜单项,可以考虑中间塞一个占位元素,或者用三个等宽的 div 配合 justify-content: space-between。还有一种技巧是给左边和右边的元素设置相同宽度,然后中间元素用 flex: 1 占满剩余空间,配合 align-items: center,菜单自然居中,不需要精确计算。
5.2 典型场景二:水平垂直居中
最近几年最常用的元素居中方案就是 Flex 的“双 center”组合,代码非常简单:
.center-box { display: flex; justify-content: center; align-items: center; }justify-content: center 解决主轴方向的水平居中,align-items: center 解决交叉轴方向的垂直居中。这个组合适用于按钮内容居中、模态框内容居中、加载占位层居中等各种场景,兼容性和稳定性都很好,也不用关心元素具体尺寸。
如果容器高度不固定,这个组合依然有效;如果容器高度撑满整个视口,效果就是常见的“居中大法”。我实际项目里几乎 90% 的居中需求都用这个方案,只有极少数需要文字基线对齐的特殊场景才会改用 baseline。需要强调一下,这个组合在任何单行情况下都能工作,不要求 flex-wrap,也不要求多行。
5.3 典型场景三:多行卡片与上中下骨架
多行卡片场景里,三个属性会一起参与。例如一个卡片容器,宽度固定,卡片按行排列,行与行之间需要均匀留白。可以用这样的组合:
.card-container { display: flex; flex-wrap: wrap; justify-content: space-between; align-content: space-between; align-items: flex-start; }这里 justify-content 控制每行内部的水平分布,让一行里的卡片两端对齐;align-content 控制行与行之间的垂直分布,让多行均匀铺开;align-items 控制每张卡片在行内的顶部对齐,避免卡片高度不一致时互相错位。
还有一种少有人用的技巧:借助 align-content: space-between 做“上中下”页面骨架。容器高度撑满视口,内部有顶部区域、中间内容区、底部区域,如果这三块在同一个 Flex 容器里且允许换行,设置 align-content: space-between 可以让他们自动分布到顶部、中间、底部,相当于一个极简的圣杯布局骨架。这个方式在某些内部管理系统里特别实用,不用写复杂的 grid 模板。
6. 高频问题与自查清单
6.1 五个高频翻车现场
第一个高频问题:设置了 align-content 却没有效果。很多人把 align-content: center 写在单行 Flex 容器里,结果垂直居中失败。排查步骤很简单:先确认容器是否有 flex-wrap: wrap,再确认是否真的有多行项目。如果项目只有一行,align-content 直接被忽略,按单行处理即可。
第二个高频问题:align-items 和 align-content 用混了。二者的核心区别是作用对象不同,align-items 针对行内项目,align-content 针对多行整体。遇到“垂直居中是哪个属性”的疑问时,先从布局是单行还是多行入手:单行用 align-items,多行想控制行间距和整体位置才用 align-content。
第三个高频问题:stretch 没生效,等高布局失败。通常是因为某个或多个子项目设置了固定的 height 或 align-self。Flex 的 stretch 本质上是“在交叉轴尺寸没有显式设置时自动伸展”,任何显式尺寸都会阻止它。排查时先逐个检查子项是否有高度设置,尤其是图片这种自带固有尺寸的元素。
第四个高频问题:baseline 看起来还是没对齐。baseline 对齐的是第一行文本基线,如果项目内包含多行文本,或者字号差异较大,基线位置会有偏移。想让它生效,最简单的方法是确保子项首行是纯文本或行内元素,并且不要在项目上设置与内容无关的 padding-bottom 影响基线位置。
第五个高频问题:某个项目不服从 justify-content 或 align-items 的设置。这个多半是该项目自己写了 margin: auto。在 Flex 布局中,margin: auto 的优先级高于容器属性,它会吞掉分配给它方向的全部剩余空间。比如容器里一个项目设置了 margin-left: auto,即使容器是 justify-content: center,该项目也会被推到右边。遇到“怎么设置都不居中”的情况,优先找子项上的 margin。
6.2 快速排查表
我把上面问题和排查思路整理成一张速查表,方便大家工作里直接对号入座:
| 现象 | 优先检查项 | 解决办法 |
|---|---|---|
| align-content 没效果 | flex-wrap 是否为 wrap | 设置 flex-wrap: wrap,并确认有多行 |
| 想垂直居中但用了 align-content | 是否为单行布局 | 单行改用 align-items: center |
| align-items: stretch 失效 | 子项是否有显式高度 | 去掉高度或用 align-self 覆盖 |
| baseline 对不齐 | 首行文本基线是否存在 | 保证文本为行内内容,不叠加过量 padding |
| 某个项目不服从容器对齐 | 子项目是否有 margin: auto | 移除该处的 margin 自动占用 |
| justify-content: center 溢出 | 项目是否超宽且 shrink 失效 | 改用 flex-start 或增加可滚动处理 |
这张表不能覆盖所有情况,但覆盖了我自己项目中遇到的大多数异常。如果你排查半天还是搞不定,建议打开浏览器开发者工具,先给容器加一个带边框的背景色,再逐个删子项确认。视觉反馈永远比在脑子里推演更快。
我个人在实际操作中的体会是,Flex 布局的三个容器属性不需要死记硬背,关键是先理解主轴和交叉轴的分工,然后记住三条边界规则:justify-content 管主轴整体,align-items 管单行内部,align-content 管多行整体,并且 align-content 必须发生在产生了换行之后。写代码的时候,先在浏览器里快速验证一下当前布局是单行还是多行,再决定用哪个属性,基本就不会走弯路。希望这篇能帮大家把这三个属性彻底拿下。