Flexbox 这套属性,我在项目里用了好几年,说实话刚开始看文档时觉得每个属性都认识,真到写布局的时候还是到处踩坑。尤其是 flex-grow、flex-shrink、flex-basis 这三个放一起时,很多人直接懵掉。这篇内容不是 MDN 的翻译稿,是按我实际排坑的经验整理出来的属性总结,把每个属性背后的计算逻辑和适用场景都掰开讲清楚。
不管你是刚学完 Flexbox 基础、想系统过一遍属性的新手,还是用了很久但偶尔被某个属性坑到的开发者,这篇都会对你有帮助。我会把容器属性、项目属性、实战案例、常见问题四个部分串起来讲,全程用代码示例说话,尽量不讲废话。
1. 先搞懂 Flexbox 的工作模型:容器和项目、主轴和交叉轴
Flexbox 全称 Flexible Box Layout Module,它的核心思路是让父容器能够改变子元素的宽度、高度和排列顺序,从而在不同屏幕尺寸下都能优雅地分布空间。理解它的前提是先弄清楚几个基础概念,因为你后面看任何属性文档都会反复遇到它们。
1.1 容器与项目:谁在管谁
Flexbox 的布局体系里只有两个角色:flex 容器(flex container)和 flex 项目(flex item)。设置了display: flex或display: inline-flex的元素就是容器,它的直接子元素自动变成项目。
这里有个容易忽略的点:只有直接子元素才会变成 flex 项目,孙元素不会。比如一个 div 里面套两层结构,内层容器里的内容还是按普通文档流走,除非你把内层也设成 flex。
<div class="container"> <div class="item">项目1</div> <div class="item">项目2</div> <div class="wrapper"> <div>我不是 flex 项目</div> </div> </div>.container { display: flex; }上面例子中wrapper本身是 flex 项目,但wrapper里的 div 不是。这个边界搞清楚后,后面看嵌套布局会省很多力气。
1.2 主轴和交叉轴:Flexbox 的方向感
Flexbox 和传统块级布局最大的区别在于它引入了主轴(main axis)和交叉轴(cross axis)的概念。主轴是 flex 项目排列的方向,交叉轴与主轴垂直。
默认情况下主轴是水平向右的,交叉轴是垂直向下的。但方向不是固定的,flex-direction一改,整个坐标系都会变。很多人在justify-content和align-items上记混,根源就是没有时刻想着当前主轴的方向。
我自己的习惯是画图理解,把主轴想象成一条马路,项目就是路上跑的车,justify-content管的是车在马路上的分布方式,align-items管的是车在垂直路面方向的停靠位置。路的方向一变,这两个属性的作用方向也跟着变。
1.3 Flexbox 适合什么场景,不适合什么场景
Flexbox 最擅长的是解决一维布局,也就是一行或一列内的空间分配和对齐问题。比如导航栏的左右分布、卡片列表的等分、元素垂直居中、按钮组对齐,这些都是它的主场。
如果遇到二维布局,也就是需要同时控制行和列的复杂网格,应该考虑 CSS Grid。Grid 操作的是行和列两个维度,Flexbox 操作的是主轴和交叉轴两个方向,前者天然适合做页面骨架,后者更贴近组件内部的元素排列。
我自己在项目里的分工很明确:整个页面的主体结构用 Grid 搭,组件内部或局部区域的排列用 Flexbox 收尾。二者配合用,比单一方案顺手得多。
2. 容器属性逐一拆解:每个值背后的小九九
这一节说容器上的属性,一共六个:flex-direction、justify-content、align-items、flex-wrap、align-content、gap。这些属性只有写在容器上才对项目生效,写在某个项目上是没用的。
2.1 flex-direction:主轴方向是布局的第一步
flex-direction 决定主轴方向,取值有四个:
row(默认):主轴水平,起点在左端row-reverse:主轴水平,起点在右端,项目从右往左排column:主轴垂直,起点在顶部,项目从上往下排column-reverse:主轴垂直,起点在底部,项目从下往上排
实际开发里 row 和 column 是绝对主力,reverse 两个值用得相对少。不过有个场景很实用:列表反转排序。比如做个聊天界面,消息列表希望最新消息在底部显示,用 column-reverse 可以配合滚动实现类似效果,省掉很多 JS 逻辑。
需要特别记住的是:reverse 不只是改变排列方向,还会改变项目的起始位置。row-reverse 时,项目的起始位置在右边,文档流顺序也会跟着反转,但 HTML 里写的前后顺序不会变,视觉上看起来像镜像翻转。
2.2 justify-content:主轴上怎么分配空间
justify-content 控制 flex 项目在主轴上的对齐方式,取值有六个:
flex-start(默认):项目紧挨主轴起点flex-end:项目紧挨主轴终点center:项目在主轴上居中space-between:第一个项目贴起点,最后一个贴终点,其余项目等间距分布space-around:每个项目两侧都有相等的空间,两端的空间是中间空间的一半space-evenly:所有项目之间的间距完全相等,包括两端
实际项目里,space-between是最常用的分布方式,因为导航栏两端对齐基本都靠它。但它有个坑:当项目换行之后,最后一行的项目会分散对齐,看起来很奇怪。后面常见问题部分我会专门讲这个。
space-around和space-evenly的区别很多人记不住,一句话总结:around 是每个项目左右间距相等,evenly 是所有间隙(包括两端)全部相等。如果你希望视觉上两端空隙和中间空隙一样大,就用 evenly。
2.3 align-items 与 align-content:交叉轴上的两套逻辑
align-items 和 align-content 都是管交叉轴的,但管的东西不一样。align-items 管的是每一行内项目在交叉轴上的对齐方式,align-content 管的是多行项目作为一个整体在交叉轴上的分布方式。
align-items 取值:
stretch(默认):项目在交叉轴方向拉伸填满容器,前提是项目没有设置固定的交叉轴尺寸flex-start:项目在交叉轴起点对齐flex-end:项目在交叉轴终点对齐center:项目在交叉轴居中baseline:项目按第一行文字的基线对齐
这里有个高频场景:不同高度的元素放在同一行,想让它们底部对齐就用flex-end,想让它们文字对齐就用baseline。比如头像加用户名列表,用 baseline 会比 center 看起来更整齐。
align-content 只有多行时才生效。如果项目没有换行(默认 nowrap),align-content 是没用的。它支持的取值和 justify-content 类似,多了 flex-start、flex-end、center、space-between、space-around、space-evenly、stretch。
一句话区分:单行内怎么对齐找 align-items,多行整体怎么分布找 align-content。
2.4 flex-wrap 与 gap:换行与间距
flex-wrap 决定项目是否换行,取值:
nowrap(默认):所有项目排成一行,即使超出容器宽度也不会换行,项目会被压缩wrap:项目在空间不足时自动换行,换行后的新行从交叉轴起点开始wrap-reverse:换行方向反转,新行从交叉轴终点开始堆积
nowrap 默认值是个容易踩坑的点。很多人给子元素设置了固定宽度,结果发现宽度失效或元素被压缩,就是因为 flex 容器默认不换行。所以做卡片列表、标签组这类需要自动换行的布局,记得显式声明flex-wrap: wrap。
gap 属性是后加的,专门控制项目和项目之间的间距,可以同时设置水平和垂直方向。gap: 10px表示行列间距都是 10px,gap: 10px 20px表示行间距 10px、列间距 20px。
在没有 gap 之前,大家普遍用 margin 或 padding 外加负边距来处理间距,总是不太干净。gap 出来后,flex 布局的间距问题清爽了很多,而且它只作用在项目之间,不会像 margin 那样产生双倍间距的问题。
2.5 容器属性速查表
| 属性 | 常用值 | 作用 | 备注 |
|---|---|---|---|
| flex-direction | row / column / row-reverse / column-reverse | 设置主轴方向 | 默认 row |
| justify-content | flex-start / flex-end / center / space-between / space-around / space-evenly | 主轴对齐与空间分配 | 项目不换行时最常用 |
| align-items | stretch / flex-start / flex-end / center / baseline | 单行项目交叉轴对齐 | 默认 stretch |
| flex-wrap | nowrap / wrap / wrap-reverse | 是否换行 | 默认 nowrap |
| align-content | 同 justify-content | 多行项目整体在交叉轴的分布 | 单行时无效 |
| gap | 长度值 | 项目间距 | 替代 margin 的最佳方案 |
3. 项目属性深度解析:flex-grow、flex-shrink、flex-basis
容器属性管的是整体布局,项目属性管的则是个体的伸缩行为。这一节是 Flexbox 的灵魂,也是最容易让人头疼的地方。
3.1 flex-basis:项目在主轴上的初始尺寸
flex-basis 决定项目在主轴方向上的初始尺寸,可以理解为项目在分配剩余空间之前的"理想大小"。默认值是auto,表示项目使用自身的尺寸(width 或内容撑开的宽度)。
它的优先级和 width 的关系很多人会搞混。当设置了 flex-basis(非 auto)时,它会覆盖主轴方向上的 width。也就是说,在水平排列的 flex 容器里,flex-basis 的优先级高于 width;在垂直排列的容器里,它优先于 height。
flex-basis 可以设具体的长度值,比如flex-basis: 200px,也可以设百分比,比如flex-basis: 30%,表示占据容器主轴尺寸的 30%。
我用过的一个典型场景是侧边栏加自适应区域:左边固定 240px,右边 flex-basis 设为 0,再设 flex-grow 让右边吃满剩余空间。这样左边永远固定,右边自动伸缩。
3.2 flex-grow:剩余空间怎么分
flex-grow 默认值是 0,表示项目不参与剩余空间的分配。当值为正数时,项目会按照比例去瓜分容器剩余的空间。
举个例子,容器宽度 900px,三个项目 flex-basis 都是 200px,基础宽度一共 600px,剩余 300px。如果三个项目的 flex-grow 分别是 1、2、3,那么剩余空间被分成 6 份,三个项目分别多得 50px、100px、150px,最终宽度变成 250px、300px、350px。
flex-grow 是按比例分配剩余空间,不是按项目自身宽度的倍数来放大,这是理解 Flexbox 伸缩机制的关键。很多新手以为 flex-grow 是宽度的倍数,看到flex-grow: 2以为比flex-grow: 1宽两倍,实际不是,它只决定额外空间的分配份额。
3.3 flex-shrink:空间不够怎么收
flex-shrink 默认值是 1,表示空间不足时项目会按比例缩小。值为 0 表示不收缩,项目保持自身尺寸,哪怕溢出容器也不缩。
这个属性和 flex-grow 正好相反,它决定容器空间不足时哪些项目先缩小、缩小多少。默认情况下所有项目都会平均缩小,但实际项目里经常有"这个板块不能缩、那个板块可以缩"的需求,这时就要用到 flex-shrink: 0。
举个例子,一个容器里有一个长文本描述和一个固定宽度的按钮,如果容器宽度不够,我们希望文本换行或收缩,但按钮保持原尺寸,就给按钮设flex-shrink: 0。
flex-shrink 的实际计算比 flex-grow 复杂一些,它会考虑项目的 flex-basis 和 shrink 值计算加权比例,不完全是简单按比例收缩。但对日常使用来说,记住"数值越大缩得越狠,0 表示不缩"就够了。
3.4 flex 简写:一行代码背后的默认值
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 0 200px:项目固定 200px 宽,不伸缩,典型固定侧边栏写法flex: 1 1 200px:项目基础 200px,可按比例伸缩
特别注意:flex: 1的 flex-basis 是 0%,意味着项目从零开始分配空间,不会先占据自身内容宽度的空间。flex: auto的 flex-basis 是 auto,项目先按内容尺寸摆好,再分配剩余空间。
这两个的差别在实际效果上很明显。同样是两个项目放同一行,flex: 1会让两者完全均分,flex: auto会让内容多的一方占据更多空间。理解了这个区别,做等分布局时就不会选错。
3.5 align-self 与 order:项目级别的微调
align-self 允许某个项目单独设置交叉轴对齐方式,覆盖容器上 align-items 的设定。取值和 align-items 一致,默认 auto 表示继承容器的值。
典型用法是:容器里大部分项目顶端对齐,某一行中间的项目需要垂直居中,这时给那个项目单独设align-self: center,不用额外包一层容器。
order 属性控制项目的排列顺序,默认值是 0,数值越小排越前。它不改变 HTML 里项目的实际顺序,只是控制视觉呈现顺序。
我踩过的一个坑是:order 只能改变 flex 项目之间的相对顺序,如果项目不是 flex 容器的直接子元素,order 失效。另外,order 改变的是整个项目的顺序,不是项目内部子元素的顺序。
4. 实战案例:四类高频布局一次说清
属性单独看都好理解,组合起来才是考验。我选了几个典型场景,把完整代码贴出来,每套代码都直接能在项目里用。
4.1 经典水平垂直居中
这是 Flexbox 最著名的能力,一套代码解决水平垂直居中,不需要 table 布局的繁琐,也不需要绝对定位加 transform 的 hack。
<div class="center-box"> <div class="card">内容</div> </div>.center-box { display: flex; justify-content: center; align-items: center; min-height: 300px; }这套方案在单行内容的场景下非常稳定,唯一需要注意的就是容器要有明确的高度。如果容器高度是 auto,交叉轴上没有多余空间,垂直居中自然就不生效。
4.2 导航栏:左右分布与中间自适应
一个典型导航栏:logo 在左,菜单在右,中间区域自适应撑开。
<nav class="navbar"> <div class="logo">Logo</div> <ul class="menu"> <li>首页</li> <li>关于</li> <li>联系</li> </ul> <div class="user">头像</div> </nav>.navbar { display: flex; align-items: center; padding: 0 20px; } .logo { flex: 0 0 auto; margin-right: auto; } .menu { display: flex; gap: 16px; list-style: none; } .user { margin-left: 20px; }要点在于给 logo 设置margin-right: auto,这样它会吃掉右边所有剩余空间,把菜单和用户区域推到最右侧。这种方式比用 space-between 更灵活,因为左侧只有一个元素时,用 space-between 无法把剩余空间全部挤到 logo 和菜单之间。
4.3 等分卡片与自适应侧栏
卡片等分布局:四张卡片均分一行,每张卡片宽度比例相同,间距统一。
.card-list { display: flex; flex-wrap: wrap; gap: 20px; } .card-item { flex: 1 1 calc(25% - 20px); min-width: 200px; }flex: 1 1 calc(25% - 20px)的意思是:基础宽度为容器四分之一减去间距,剩余空间再按比例均分。加上 min-width 是防止屏幕太窄时卡片挤得太小,这时会自然换行。
自适应侧栏的场景是:左侧固定 240px,右侧自动填充剩余宽度。
.container { display: flex; } .sidebar { flex: 0 0 240px; } .content { flex: 1; min-width: 0; }这里有个很容易忽略的关键点:content 要设置min-width: 0。如果不设,flex 项目默认的 min-width 是 auto,即项目最小宽度不小于内容宽度。当内容里有长文本或较宽的表格时,项目会被内容撑开,导致布局溢出。这个坑我在后面排坑部分还会详细讲。
4.4 粘性底部布局:footer 永远贴底
页面内容不足一屏时,footer 要固定在底部;内容超过一屏时,footer 自然排在内容后面。这个需求用 Flexbox 实现非常简单。
<div class="page"> <div class="main">主要内容</div> <div class="footer">底部信息</div> </div>.page { display: flex; flex-direction: column; min-height: 100vh; } .main { flex: 1; } .footer { flex: 0 0 auto; }原理是利用了 flex 项目在主轴(垂直方向)上的放大能力:main 区域的 flex-grow 为 1,会把剩余高度全部吃掉,footer 自然被推到最底部。无论内容多少,这个结构都成立。
5. 常见问题与排坑实录
这一节分享的是我实际开发中反复遇到的坑,每个问题背后都有具体的踩坑经验,不是从文档里抄来的。
5.1 flex-basis 和 width 到底谁说了算
当项目同时设置了 width 和 flex-basis 时,水平排列的情况下 flex-basis 优先。这意味着你可能写了 width: 200px,结果项目实际的初始尺寸却是 flex-basis 的值。
这个问题最容易出现在从老项目迁移或维护别人代码的场景。解决办法很简单:要么只保留一个,要么明确两个值的关系。如果你想用 width 控制尺寸,就把 flex-basis 显式设为 auto。
5.2 min-width 闹出的溢出问题
flex 项目默认的 min-width 是 auto,这意味着项目的最小尺寸至少等于内容的最小宽度。当一个 flex 项目里有一串很长的英文单词或没有换行机会的 URL 时,项目会被内容撑开,就算设置了 flex-basis、flex-shrink 也挡不住。
解决办法就是给项目加min-width: 0,允许项目收缩到比内容更窄的尺寸。或者给内部文本加overflow-wrap: break-word,让长文本可以换行。这两招组合使用基本能解决绝大多数文本溢出问题。
这个坑我遇到不下三次,每次都是检查半天才发现是 min-width 的锅。它不像其他属性那么显眼,但破坏力极强。
5.3 space-between 换行后的末端对齐问题
一个容器设置了flex-wrap: wrap,同时justify-content: space-between,8 个卡片一行排 4 个,理想情况下是两行各 4 个。但如果最后一行只剩 3 个,这 3 个会按照 space-between 的规则分散开,而不是紧贴左侧。
解决办法有两个:
一是用flex-grow配合flex-basis计算宽度,让每个项目在剩余空间里按比例放大,最后一行不够时自然留白而不是分散。
二是给所有项目设置统一的宽度百分比,绕开 space-between。比如每个项目宽度设为 23%,配合 gap 使用,让换行后的项目自然左对齐,同时保留合理的间距。
5.4 文本省略号失效
想让 flex 项目里的文字超出一行显示省略号,很多人写法和普通 div 一样,结果发现不生效。
原因是 flex 项目默认可以压缩和伸展,文本节点不会自动允许溢出省略。要正常显示省略号,需要组合三个条件:项目要有确定的宽度约束(flex-basis 或 width)、设置overflow: hidden、同时在文本元素上设置white-space: nowrap和text-overflow: ellipsis。
.text-ellipsis { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }关键还是min-width: 0,没有它,项目被内容撑开,省略号自然无从谈起。
5.5 常见问题速查表
| 问题 | 原因 | 解决办法 |
|---|---|---|
| 项目宽度失效 | 默认 nowrap,项目被压缩 | 检查 flex-shrink,设置 flex-wrap 或 flex-basis |
| 内容撑破容器 | min-width 默认为 auto | 给项目设 min-width: 0 |
| 最后一排不齐 | space-between 导致分散 | 改用固定百分比宽度或 flex-grow 方案 |
| 文本省略号不出现 | 项目没有宽度约束 | 组合设置 flex: 1、min-width: 0、overflow: hidden |
| footer 不贴底 | 容器高度不是全屏 | 使用 min-height: 100vh 加 flex-direction: column |
| 垂直居中无效 | 容器高度为 auto | 给容器设置明确高度或 min-height |
| gap 不生效 | 浏览器版本过老 | 检查浏览器兼容性,必要时用 margin 替代 |
写到这里,Flexbox 的核心属性基本都覆盖完了。回头再看这套属性系统,它其实不复杂,关键是要有一个清晰的坐标系意识:时刻清楚主轴在哪个方向、哪个属性作用在哪个轴上、项目尺寸是如何被三个 flex 属性共同决定的。
我个人在实际项目里摸索出的经验是:不要在脑袋里背属性的取值列表,而要把重点放在理解"剩余空间"这个概念上。Flexbox 的绝大多数行为,不管是 grow、shrink 还是 space-between、space-around,本质都是在处理空间分配问题。空间从哪来、到哪里去、按什么规则分,把这几个问题想清楚,Flexbox 就不会再是玄学。真的被某个布局卡住时,试着把一个复杂场景拆成两层来做,外层管大方向,内层管小细节,配合 flex 属性和 min-width、gap 几个容易被忽略的值,大部分问题都能顺利解决。