做前端这十来年,布局问题始终是最容易被低估的一环。很多需求看着是"样式微调",本质上是布局模型没选对。Flex 布局(弹性盒子布局)可以说是过去十年里前端样式领域影响最深远的一次范式切换——它把"盒子的位置和大小由父容器分配"这件事变成了默认规则,而不是靠浮动、行内块、负边距这些技巧硬凑。这篇内容想聊的不是 API 清单,而是一个实际做项目的人怎么用 Flex 布局把需求拆干净:什么场景该用它、主轴交叉轴到底怎么想、flex-grow/flex-shrink/flex-basis这几个参数怎么算、几个高频界面到底怎么写、以及我踩过的那些"代码看起来没问题但就是不生效"的坑。适合刚接触 CSS 不久需要一套完整心智模型的新手,也适合写了很多年但一直靠试参数过日子的同行——后者往往收获更大,因为 Flex 布局真正的门槛从来不是属性名,而是脑子里那根轴。
1. 布局方案为什么最后收敛到 Flex 布局
1.1 从一段被浮动支配的日子说起
早年的三栏布局基本是这么写的:左栏float: left、右栏float: right、中间栏靠margin让位,父容器还得加一个伪元素清浮动,否则高度塌陷。垂直居中更麻烦,要么用line-height等于高度(只适合单行文本),要么把父容器变成display: table-cell配合vertical-align: middle(会破坏文档流的其他假设),要么用绝对定位加translate(-50%, -50%)(需要一个确定尺寸的祖先)。这些方案单看都能用,但它们有一个共同的毛病:布局的意图和实现手段是分离的。你想表达的是"这两块各占一半",实际写出来的是"宽度 50%、浮动、清浮动、防止折行"。中间任何一环被别的样式干扰,整个结构就散了。
Flex 布局改变的正是这一点。它把"父容器怎么分配子元素的位置和空间"这件事变成了一等公民,display: flex一加上去,子元素自动进入"由父级统一调度"的模式,浮动、清浮动、行内块的空白间隙这些历史包袱一次性消掉。这不是语法糖,是模型换了。
1.2 Flex 布局真正解决的三类问题
我把过去用 Flex 布局解决的问题归了三类,基本覆盖日常九成场景。第一类是剩余空间的分配,比如导航栏里 logo 固定、菜单居中、右侧按钮贴边,中间那段要吃掉所有空余宽度;第二类是等高档位,比如一排卡片,内容长短不一但视觉上必须底部对齐、边框齐平,用浮动得靠padding-bottom加负margin-bottom这种黑魔法来伪造,Flex 布局默认的align-items: stretch直接就给你了;第三类是同一条轴上的对齐与顺序控制,包括居中、两端对齐、间隔均分,以及用order在不改 DOM 的情况下调整视觉顺序。
这三类问题有个共同点:它们的答案都是"相对某个容器"的,而不是"绝对坐标"。Flex 布局的模型天然贴合这种相对思维,所以写起来顺。
1.3 什么时候不该用 Flex:和 Grid 的边界
这里必须说清楚一个被讲烂但依然很多人模糊的点。Flex 布局是一维模型:它一次只处理一条轴上的分配,另一条轴上是"顺带的"对齐。Grid 是二维模型:行和列同时被定义。判断标准很简单——如果你能一眼看出"这是一排"还是"这是一片",那是一维,用 Flex;如果需要"同时按行和列对齐"(比如一个不规则的仪表盘、一个需要严格对齐的表格式卡片墙),那是二维,用 Grid。
还有一个更实用的判据:内容驱动还是布局驱动。内容驱动指的是"子元素的尺寸由内容决定,我只负责把它们排开",比如标签列表、按钮组、导航菜单——Flex 布局最擅长。布局驱动指的是"我先划好网格,内容往里填",比如后台的页头 + 侧栏 + 主体 + 页脚结构——Grid 更省事。我自己的习惯是:整体骨架用 Grid 或 Flex 都行,局部组件优先 Flex,因为组件内部的元素数量往往动态变化,Flex 的自动分配更抗变化。
2. 主轴与交叉轴:理解 Flex 布局的唯一钥匙
2.1 轴是跟着方向转的,别死记"水平垂直"
新手最容易卡住的地方,是把justify-content记成"水平对齐"、align-items记成"垂直对齐"。这个记忆方式在默认的flex-direction: row下是对的,一旦换成column就全反了,然后开始怀疑人生。正确的说法是:主轴由flex-direction决定,justify-content永远作用于主轴;交叉轴垂直于主轴,align-items永远作用于交叉轴。轴转了,两个属性的语义跟着转,不需要额外记。
我用一个类比帮人理解:把容器想象成一个抽屉,子元素是里面的一排卡片。flex-direction决定卡片是横着排还是竖着排,这就是主轴方向。justify-content管的是"沿排列方向怎么分空间",align-items管的是"垂直于排列方向怎么对齐"。
flex-direction | 主轴方向 | justify-content控制 | align-items控制 |
|---|---|---|---|
| row(默认) | 水平从左到右 | 水平分布 | 垂直对齐 |
| row-reverse | 水平从右到左 | 水平分布(方向反转) | 垂直对齐 |
| column | 垂直从上到下 | 垂直分布 | 水平对齐 |
| column-reverse | 垂直从下到上 | 垂直分布(方向反转) | 水平对齐 |
记住这张表比背任何口诀都管用。另外提醒一句,row-reverse和column-reverse只是视觉反转,DOM 顺序和键盘 Tab 顺序不变,这在做可访问性的时候是隐患,后面第 5 章会细说。
2.2 容器属性和项目属性的分工
Flex 布局的属性分两组,很多人写着写着就混了。容器属性作用于display: flex的那一层,管的是"怎么排";项目属性作用于直接子元素,管的是"自己怎么表现"。这个分工必须清晰,否则会出现"我在子元素上写justify-content没反应"这种低级困惑——因为justify-content是容器属性,写在子元素上无效。
| 类别 | 属性 | 作用 |
|---|---|---|
| 容器 | display: flex / inline-flex | 开启弹性上下文 |
| 容器 | flex-direction | 决定主轴方向 |
| 容器 | flex-wrap | 是否换行,换行方向 |
| 容器 | justify-content | 主轴上的分布方式 |
| 容器 | align-items | 单行时交叉轴对齐 |
| 容器 | align-content | 多行时行与行之间的分布 |
| 容器 | gap | 项目之间的间距(替代 margin 方案) |
| 项目 | flex-grow | 剩余空间怎么放大 |
| 项目 | flex-shrink | 空间不足怎么收缩 |
| 项目 | flex-basis | 分配前的初始尺寸 |
| 项目 | order | 视觉顺序 |
| 项目 | align-self | 单独覆盖交叉轴对齐 |
有个细节值得单独提:align-items和align-content的区别不是"单行/多行"这么简单。align-items管的是每个项目在自己那一行里怎么对齐,align-content管的是多行之间怎么分布。只有开了flex-wrap: wrap并且真的换行了,align-content才有效果。这也是个高频困惑点,很多人写了align-content: center没反应,就是因为没换行。
2.3 flex-grow、flex-shrink、flex-basis 到底怎么算
这三个参数是 Flex 布局里最容易被"凭感觉调"的部分,但只要把计算规则讲清楚,其实非常好推断。flex-basis是分配前的基准尺寸,可以理解为"这个项目本来想多大"。flex-grow是剩余空间按什么比例分给它。flex-shrink是空间不够时按什么比例从它身上扣。
先看放大的例子。容器宽 600px,里面两个项目:A 的flex-basis是 100px、flex-grow是 1;B 的flex-basis是 200px、flex-grow是 2。基准尺寸加起来 300px,剩余空间是 600 − 300 = 300px。这 300px 按 grow 的比例 1:2 分,A 拿 100px、B 拿 200px,最终 A 是 200px、B 是 400px。
这里有个关键认知:grow 分的是"剩余空间",不是"总宽度"。所以你不能指望flex-grow: 2的项目宽度就是flex-grow: 1的两倍——只有基准尺寸为 0 的时候才成立。这解释了为什么flex: 1能让多个项目严格等分:因为它把flex-basis设成了 0,剩余空间就是全部宽度,按相同的 grow 分自然等分。
再看收缩。容器 300px,两个项目基准宽度都是 200px,flex-shrink都是 1。基准总和 400px,溢出 100px。收缩量不是平均分的,而是按flex-shrink×flex-basis加权分配:权重分别是 200×1 和 200×1,总权重 400。A 要缩 100 × (200/400) = 50px,变成 150px;B 同理。如果 B 的flex-shrink改成 2,权重变成 200 和 400,总权重 600,A 缩 100 × (200/600) ≈ 33.3px 变成 166.7px,B 缩 66.7px 变成 133.3px。
这个加权规则很重要,它解释了"我明明把flex-shrink调大了,为什么收缩幅度没想象中大"——因为基准宽度也在权重里,基准宽的项目天然承担更多收缩量。
注意:
flex-shrink的收缩有下限,默认情况下项目不会收缩到内容的最小尺寸以下。这个下限来自min-width: auto(或min-height: auto),第 4 章会专门讲这个坑。
2.4 flex 简写的隐藏差异
flex是个简写属性,但它有几个容易被忽略的默认值填充规则,如果不搞清楚,会出现"我写flex: 1和写flex: 1 1 auto效果不一样"的诡异现象。
| 写法 | 展开后 | 实际效果 |
|---|---|---|
flex: 1 | 1 1 0% | 基准 0,严格按 grow 等分 |
flex: auto | 1 1 auto | 基准是内容宽度,剩余空间再分 |
flex: none | 0 0 auto | 不放大不收缩,尺寸由内容决定 |
flex: 0 1 auto | 同左 | 默认值,内容撑开但可以收缩 |
flex: 2 1 200px | 同左 | 基准 200px,按 2 的比例分剩余 |
最典型的差异是flex: 1和flex: auto。假设一行里有三个项目,内容文字长度差别很大。用flex: 1,三者等宽,内容短的会留很多空白;用flex: auto,内容长的先占住自己的宽度,剩余空间再三等分,最终宽度各不相同。选哪个取决于你要的是"视觉整齐"还是"内容优先"。
还有一个更细的点:flex-basis: 0和flex-basis: 0%在绝大多数情况下表现一致,但在某些老版本内核里百分比基准的解析行为有差异。现在的项目里我统一写0%,兼容性更稳。
3. 高频场景实操:从居中到整页骨架
3.1 居中三件套:写一次记一辈子
水平垂直居中是最经典的需求,Flex 布局里三行代码搞定,而且不需要知道任何一方的尺寸:
.center { display: flex; align-items: center; justify-content: center; }这个写法的好处是对子元素尺寸完全无感,无论里面是一个固定 200×100 的弹窗,还是一段文字,还是多行内容,都能居中。对比早期的绝对定位方案,不需要position: relative祖先,不需要知道宽高,也不会有transform引起的模糊问题(在某些设备上,translate(-50%, -50%)落在半像素上会让文字发虚,这个我在实际项目里遇到过不止一次)。
如果是整屏居中,父容器还要加上min-height: 100dvh。这里用dvh而不是vh,是为了避开移动端浏览器地址栏收起展开导致的视口高度跳变——vh在某些移动端浏览器里指的是"地址栏隐藏时的高度",页面一加载就会内容溢出。这个细节不写进代码里,上线后一定被用户反馈。
3.2 导航栏:固定 + 自适应 + 贴边的三段式
导航栏的典型结构是:左边 logo 固定宽度,中间菜单占据剩余空间,右边操作按钮贴边。很多人的第一反应是给中间加flex: 1,但更稳的做法是用margin-left: auto推挤,避免中间区域被强行拉伸后内部元素跟着变形。
<nav class="nav"> <div class="nav__logo">Logo</div> <ul class="nav__menu"> <li><a href="/">首页</a></li> <li><a href="/docs">文档</a></li> <li><a href="/about">关于</a></li> </ul> <div class="nav__action"> <button>登录</button> </div> </nav>.nav { display: flex; align-items: center; gap: 32px; height: 60px; padding: 0 24px; border-bottom: 1px solid #e5e5e5; } .nav__logo { flex: 0 0 auto; font-weight: 600; } .nav__menu { display: flex; align-items: center; gap: 24px; margin: 0; padding: 0; list-style: none; flex: 1 1 auto; min-width: 0; } .nav__action { flex: 0 0 auto; margin-left: auto; }几个点值得说明。flex: 0 0 auto的意思是"不放大、不收缩、尺寸按内容",用在 logo 和按钮上最合适,因为它们不应该被挤压。min-width: 0加在菜单上是为了防止菜单项过多时把整个导航栏撑破——默认的min-width: auto会让容器至少和内容一样宽,加上这个才能真的被压缩。margin-left: auto在这里比justify-content: space-between更精确,因为space-between会把三个区域均匀推开,而实际需求通常是"菜单紧跟 logo,按钮贴右边"。
3.3 卡片列表:等宽、等高、最后一行怎么处理
卡片列表是 Flex 布局里最容易出问题的场景,因为它同时涉及换行、等宽、等高三个诉求。
.cards { display: flex; flex-wrap: wrap; gap: 16px; } .card { flex: 1 1 240px; max-width: 320px; display: flex; flex-direction: column; border-radius: 8px; overflow: hidden; background: #fff; } .card__body { flex: 1 1 auto; padding: 16px; } .card__footer { flex: 0 0 auto; padding: 12px 16px; }flex: 1 1 240px的含义是"基准 240px,可放大可收缩",配合flex-wrap: wrap,浏览器会尽量每行塞满,塞不下就换行。max-width: 320px是防止项目数少的时候卡片被拉得过大——比如只有两张卡片,撑满整行会很难看。这套写法的问题在于最后一行的卡片会被拉伸得比其他行宽,因为 grow 在起作用。
如果视觉上要求所有行严格等宽,就别用 grow,改用百分比基准:
.cards { display: flex; flex-wrap: wrap; gap: 16px; } .card { flex: 0 0 calc((100% - 32px) / 3); }这里的32px是两倍 gap(三列之间有两个间隙)。公式是(100% - gap × (列数 - 1)) / 列数。写死三列的问题是响应式不好做,配合媒体查询改比例即可。至于"最后一行居中"这种需求,Flex 布局本身做不到——justify-content: center会让每一行都居中,不只是最后一行。真要实现,要么用 Grid 的repeat(auto-fill, minmax(240px, 1fr)),要么在末尾塞入不可见的占位元素凑满一行。我一般直接建议产品换成 Grid 或者接受最后一行左对齐,为了一个视觉细节增加十几个占位节点不划算。
卡片的等高是靠display: flex; flex-direction: column加内部flex: 1 1 auto实现的:外层 Flex 保证所有卡片被拉伸到同一高度,内层纵向 Flex 保证正文区域吃掉剩余空间,页脚被推到最底部。这套"外层横排、内层纵排"的组合拳几乎适用所有统一视觉高度的卡片场景。
3.4 侧栏 + 主体的自适应两栏
后台系统最常见的骨架是左侧固定宽度导航、右侧内容自适应:
.layout { display: flex; min-height: 100vh; } .layout__side { flex: 0 0 240px; } .layout__main { flex: 1 1 auto; min-width: 0; }min-width: 0依然是关键,尤其是右侧内容里放了表格或者长链接的时候。不加这一行,表格的min-content宽度会把整个右侧撑开,导致左侧被挤到屏幕外,出现横向滚动条。这个 bug 我帮人排查过至少五次,每次都是同一个原因。
侧栏需要收起的时候,把flex-basis从 240px 改成 64px,加一个transition: flex-basis 0.2s就有动画了。但注意,flex-basis的过渡会触发逐帧重排,在低性能设备上可能掉帧。更稳的做法是给侧栏设定固定宽度,然后用transform: translateX()把它移出去,视觉效果一样但走的是合成层。
3.5 表单行:标签固定、输入框撑满
表单里"标签右对齐、输入框撑满剩余空间"是另一个高频需求,用 Flex 布局写起来非常干净:
.field { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .field__label { flex: 0 0 96px; text-align: right; color: #666; } .field__control { flex: 1 1 auto; min-width: 0; } .field__control input { width: 100%; box-sizing: border-box; }这里的min-width: 0依然是防止输入框被内容撑开。box-sizing: border-box是让width: 100%包含内边距和边框,不然加了 padding 之后输入框会溢出容器几像素——这种 1~2px 的溢出在视觉上看不出来,但会引发横向滚动条,排查起来很费劲。
多行文本域还有个额外坑:textarea有默认的resize手柄,在某些浏览器里会给容器算出额外的宽度。稳妥的做法是加resize: vertical限制只能纵向拖动,或者显式设display: block。
3.6 移动端底部操作条:吸底 + 安全区
移动端页面常见的结构是"内容区可滚动,底部操作条固定在下方"。用纵向 Flex 布局写:
.page { display: flex; flex-direction: column; min-height: 100dvh; } .page__main { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; } .page__bar { flex: 0 0 auto; padding: 12px 16px; padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); background: #fff; border-top: 1px solid #eee; }核心是.page__main上的min-height: 0。纵向 Flex 容器里,子元素的min-height默认是auto,它会让元素至少和内容一样高,于是内容一多就不滚动,整个页面被顶长。加上min-height: 0才能让overflow-y: auto真正生效。这个坑在前面横向场景里对应的是min-width: 0,本质是同一个机制。
安全区用env(safe-area-inset-bottom)处理,配合calc叠加基础内边距,0px作为兜底值保证不支持的环境不会算出无效值。
4. 那些"代码看着没错但不生效"的 Flex 布局坑
4.1 为什么子元素不肯收缩
这是 Flex 布局里出现频率最高的困惑:给项目设置了flex-shrink: 1,容器变窄时它却纹丝不动,继续把容器撑破。原因在于 Flex 项目的min-width(纵向是min-height)默认值是auto,这个auto在 Flex 上下文里的含义是"不小于内容的最小尺寸"。对于一段不能折行的文本,最小尺寸就是整段文字的宽度,所以它拒绝变小。
解决办法是显式覆盖:min-width: 0或者min-width: 1px(老写法,现在不需要了)。配合文本省略号的话,标准三件套是这样的:
.ellipsis { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }很多人只写了后三个属性发现省略号不出现,就是因为缺了min-width: 0。这个知识点我建议直接背下来,因为它每年都会在项目里出现几次。
4.2 高度塌陷与"父级不给高度我就不滚动"
纵向布局里flex: 1加overflow: auto不滚动的现象,第四章已经提过一次,这里展开说原理。flex: 1展开是1 1 0%,它只决定了"能长多大",不决定"能长到多少就不长"。父容器如果没有确定高度(比如height: 100%没生效),子元素就会跟着内容一起无限长高,自然不需要滚动。
正确的做法是给链条上每一层都设一个高度约束:最外层height: 100vh或100dvh,中间层flex: 1 1 auto; min-height: 0,滚动层overflow-y: auto。三者缺一不可。我画过一个检查清单贴在项目文档里:最外层有没有明确高度 → 中间每个 Flex 项目有没有min-height: 0→ 滚动容器是不是真的设了overflow。按这个顺序查,基本一次就能定位。
4.3 margin: auto 在 Flex 里的特殊行为
在普通块级布局里,margin: 0 auto能水平居中,但纵向 auto 会被算成 0。在 Flex 上下文里,margin: auto会吸收该轴上的剩余空间,横竖都有效。这就是为什么margin-left: auto能把元素推到最右,margin-top: auto能把元素压到最底部。
这个特性很好用,但也容易误伤。比如给一个容器设了justify-content: space-between,同时又给最后一个元素加了margin-left: auto,两个机制叠加,中间间距会变得非常诡异。我的经验是:同一组元素上,margin: auto和justify-content不要同时用来做分布,选一个就好。用margin: auto的场合更适合"单个元素定位",比如导航栏右侧按钮贴边、卡片底部的操作区压底。
4.4 图片、宽高比与拉伸
图片放在 Flex 项目里经常被拉伸变形。原因在于align-items: stretch是默认值,如果图片的直接父级是 Flex 项目,它会试图占满交叉轴的高度。修法有两种:给图片align-self: center(或者容器align-items: flex-start),或者给图片设object-fit: cover加height: 100%。
涉及固定比例的头像、封面图,我倾向于用aspect-ratio显式声明比例,然后让object-fit: cover去裁切:
.avatar { flex: 0 0 48px; aspect-ratio: 1; border-radius: 50%; object-fit: cover; }flex: 0 0 48px保证不会被压缩,aspect-ratio: 1保证是正方形。这套写法在列表项场景里非常稳,不会再出现"头像变成椭圆"的问题。
4.5 常见问题速查表
| 现象 | 大概率原因 | 处理方式 |
|---|---|---|
| 子元素被撑破,容器溢出 | min-width/min-height默认auto | 加min-width: 0或min-height: 0 |
| 省略号不显示 | 缺少min-width: 0 | 补齐四件套 |
flex: 1后内容不滚动 | 父级无明确高度 | 逐层加高度约束 |
纵向align-content无效 | 没有开flex-wrap | 加flex-wrap: wrap |
| 项目间距不对称 | gap与margin混用 | 统一用gap,删掉子元素的单向 margin |
| 卡片高度不齐 | 没走内层纵向 Flex | 外层横排 + 内层column+ 内容区flex: 1 |
| 视觉顺序与 DOM 不符 | 用了order或*-reverse | 检查键盘 Tab 顺序与屏幕阅读器 |
| 输入框撑破容器 | 未设box-sizing: border-box | 全局重置里统一设置 |
5. 把 Flex 布局落进工程规范里
5.1 用原子类约定减少重复劳动
项目里如果每个组件都从零写display: flex; align-items: center,代码会非常碎。我的习惯是沉淀一小套原子类,只保留最高频的十几个:
:root { --space-unit: 4px; } .u-row { display: flex; flex-direction: row; align-items: center; } .u-col { display: flex; flex-direction: column; } .u-between { justify-content: space-between; } .u-center { justify-content: center; align-items: center; } .u-grow { flex: 1 1 auto; min-width: 0; } .u-shrink-0 { flex-shrink: 0; } .gap-1 { gap: calc(var(--space-unit) * 1); } .gap-2 { gap: calc(var(--space-unit) * 2); } .gap-4 { gap: calc(var(--space-unit) * 4); }关键在.u-grow里面把min-width: 0一起带上。这是把踩坑经验固化成默认值——单独一个flex: 1的类就是半个半成品,早晚出问题。这个做法我坚持了好几年,效果是新人不会再踩min-width那个坑。
间距用 CSS 变量乘倍数,好处是产品改间距规范的时候只改一个变量,全站跟着走,不用满仓库搜16px。
5.2 用开发者工具把 Flex 布局"看见"
现代浏览器的开发者工具里,Flex 容器旁边会有一个小小的flex徽章,点开能看到完整的flex-direction、justify-content、align-items可视化开关,还能直接在面板里拖动参数看实时效果。更重要的是,元素检查面板里会直接显示每个项目的flex-grow、flex-shrink、flex-basis的最终计算值,这比自己按公式算快得多。
我排查布局问题的顺序固定是三步:先看徽章确认容器真的是 Flex(有时候是display被别的类覆盖了);再看项目的flex-basis实际算出来是多少(可能是auto拿到了意外的内容宽度);最后看min-width的实际值(这是最隐蔽的)。三步走完,九成问题能定位。
还有一个实用技巧:在调试的时候给容器临时加outline: 1px solid red而不是border,因为outline不占布局空间,不会反过来影响你正在排查的尺寸问题。用border调试经常会把自己带沟里。
5.3 动画与性能:别去动 flex-grow
有个反直觉的点:flex-grow、flex-basis这些属性不能被 GPU 加速,给它们加 transition 会导致每一帧都重新计算布局,在低端设备上肉眼可见地卡。如果要做"侧栏展开收起""卡片宽度变化"这类动画,正确姿势是:
- 侧栏收起:固定宽度,用
transform: translateX(-100%)移出,配合transition: transform; - 卡片宽度变化:用
transform: scaleX()或者外层固定尺寸、内层做缩放; - 万不得已要动宽度,用
width而不是flex-basis,性能略好一点,但依然是重排。
如果一定要让布局属性动起来,记得给动画时长控制在 200ms 以内,并且避免同时动太多元素。
还有一个可访问性上的注意点:order属性和row-reverse只改变视觉顺序,DOM 顺序和键盘 Tab 顺序不变。这意味着视觉上第一个的元素,用键盘可能最后一个才能聚焦到。如果页面内容是表单或导航,这种不一致会让依赖键盘和屏幕阅读器的用户非常困惑。我的原则是:能用 DOM 顺序解决就不改视觉顺序,确实需要调整的,同步调整 DOM 结构。
gap属性值得一提,它现在是 Flex 布局里做间距的首选,比给子元素加margin-right再处理最后一个元素干净得多。但要注意,老版本浏览器对 Flex 的gap支持晚于 Grid 的gap,如果项目还要照顾较老的环境,得准备好margin方案的降级写法。
最后分享一个我实际项目里养成的小习惯:每写完一个 Flex 容器,就在本地把窗口从最宽拖到最窄一遍,看看有没有出现横向滚动条、有没有元素被压变形、有没有文字溢出。这个动作只需要十几秒,但能拦住绝大多数响应式相关的问题,比等测试提 bug 再返工的成本低太多。