Web

别再死记 flex: 1 了:从空间分配彻底搞懂 Flex

Flex 可视化实验场 拖动参数,观察浏览器如何分配空间
0容器
A
B
C
当前写法
基础尺寸总和
容器剩余空间

前端开发中,display: flex 几乎已经成为最常用的 CSS 布局方式之一。

但有一个很有意思的现象:很多工作了几年的前端开发,甚至每天都在使用 Flex 的人,遇到下面三个属性时依然容易混淆:

flex-grow
flex-shrink
flex-basis

更典型的是:

flex: 1;

和:

flex: auto;

到底有什么区别?

很多时候我们会直接记住一句话:

flex: 1 就是平均分配。

这句话在很多简单场景下确实“看起来是对的”,但它没有解释为什么。

其实理解 Flex 的关键并不在于死记三个属性,而在于把它看成一个空间分配问题

先确定每个 Flex Item 的基础尺寸,再计算容器剩余多少空间,最后决定这些空间应该增长还是收缩。

下面的交互式 Demo 可以边改参数边观察这个过程。

一、先把 Flex 想成“分配空间”

假设有一个 600px 宽的容器,里面有三个元素 A、B、C。

浏览器并不是简单地看到“三个元素”,然后直接做:

600 ÷ 3 = 200px

它首先需要回答一个问题:

每个元素本来应该有多大?

这个“基础尺寸”就是 Flex 中非常重要的概念:Flex Base Size

flex-basis 正是影响这个基础尺寸的属性。

之后浏览器再计算:

容器空间 - 所有 Item 的基础尺寸

结果可能是两种情况。这里用 Mermaid 把这个过程画出来:

flowchart LR
    A[Flex Container] --> B[计算 Flex Base Size]
    B --> C[flex-basis]
    C --> D{空间是否足够?}
    D -->|剩余空间 > 0| E[flex-grow]
    D -->|剩余空间 < 0| F[flex-shrink]
    E --> G[分配多余空间]
    F --> H[分担空间不足]
    G --> I[最终尺寸]
    H --> I

所以可以先记住这句话:

flex-basis 决定从哪里开始算,flex-grow 决定多出来的怎么分,flex-shrink 决定不够的时候怎么缩。

二、flex-basis:我本来想要多大?

最简单的情况:

.item {
  flex-basis: 200px;
}

可以把它理解成:

在 Flex 主轴方向上,我的基础尺寸是 200px。

例如容器宽度为 600px,三个 Item 都设置 flex-basis: 200px

A = 200px
B = 200px
C = 200px

总计 = 600px

刚好填满。

如果容器改成 800px:

A = 200px
B = 200px
C = 200px

已经使用 = 600px

剩余 = 200px

这时候就轮到 flex-grow 出场了。

三、flex-grow:有多余空间,怎么分?

假设容器宽度为 800px,三个 Item 的 flex-basis 都是 200px。

三个元素先占:

200 + 200 + 200 = 600px

容器有 800px,所以剩余:

800 - 600 = 200px

如果三个元素都是:

.a { flex-grow: 1; }
.b { flex-grow: 1; }
.c { flex-grow: 1; }

那么 200px 按照 1 : 1 : 1 分配,最终大约:

A = 266.67px
B = 266.67px
C = 266.67px

如果改成:

.a { flex-grow: 1; }
.b { flex-grow: 2; }
.c { flex-grow: 3; }

那么剩余空间按照 1 : 2 : 3 分配。

因此 flex-grow 真正表达的不是“我的宽度是多少”,而是:

如果还有剩余空间,我按照多大的比例参与分配。

四、flex-shrink:空间不够,怎么缩?

flex-grow 解决的是“空间多了怎么办”,flex-shrink 解决的则是“空间不够怎么办”。

例如容器只有 500px,而三个 Item 的基础尺寸都是 200px:

200 + 200 + 200 = 600px

需要压缩:
600 - 500 = 100px

如果三个元素都是:

flex-shrink: 1;

在基础尺寸相同的情况下,最终大约是:

A = 166.67px
B = 166.67px
C = 166.67px

这里有一个很容易被忽略的细节:

flex-shrink 不是简单地说“数值越大,就直接缩多少像素”。

实际的收缩计算还会考虑 Flex Item 的基础尺寸。可以粗略理解为收缩权重与:

flex-shrink × flex-basis

有关。

例如:

.a {
  flex-basis: 100px;
  flex-shrink: 1;
}

.b {
  flex-basis: 300px;
  flex-shrink: 1;
}

如果两者共同面对空间不足,B 的基础尺寸更大,因此通常承担的收缩量也会更大。

所以:

flex-shrink 更准确地说是“空间不足时的收缩因子”,而不是一个直接的缩小像素值。

五、三个属性放在一起就清楚了

属性解决什么问题可以理解成
flex-basis初始尺寸是多少我本来想要多大?
flex-grow空间有剩余怎么办有多的,多给我多少?
flex-shrink空间不够怎么办不够的时候,我承担多少?

整个过程可以理解成:

flowchart TD
    A[Flex Item] --> B[确定 Flex Base Size]
    B --> C[读取 flex-basis]
    C --> D{计算剩余空间}
    D -->|正数| E[Positive Free Space]
    D -->|负数| F[Negative Free Space]
    E --> G[按 flex-grow 分配]
    F --> H[按 flex-shrink × flex-basis 计算收缩权重]
    G --> I[得到最终尺寸]
    H --> I

这才是理解 Flex 的核心。

六、flex 是什么?

平时我们写:

flex: 1;

其实 flex 是一个简写属性,它同时设置:

flex-grow
flex-shrink
flex-basis

对于最常见的:

flex: 1;

在实际布局中可以理解为:

flex: 1 1 0%;

也就是:

flex-grow   = 1
flex-shrink = 1
flex-basis  = 0%

注意最后这个 0%,它其实就是理解 flex: 1 的钥匙。

七、为什么 flex: 1 会“平均分配”?

假设容器宽度为 600px:

.item {
  flex: 1;
}

三个 Item 都以相同的 grow 比例参与空间分配,而 basis 从 0 开始,因此最终得到:

flowchart LR
    A[600px 容器] --> B[三个 Item 的 basis 都为 0]
    B --> C[剩余空间约 600px]
    C --> D[flex-grow: 1 : 1 : 1]
    D --> E[A = 200px]
    D --> F[B = 200px]
    D --> G[C = 200px]

所以真正值得记住的是:

flex: 1 并不是一个神奇的“平均分配”属性,而是让项目以 0 基础尺寸参与空间分配。

“平均分配”只是 grow 相同情况下得到的结果。

八、flex: 1 和 flex: auto 到底差在哪里?

现在来看最容易混淆的一对:

flex: 1;

和:

flex: auto;

flex: auto 可以理解成:

flex: 1 1 auto;

也就是:

flex-grow   = 1
flex-shrink = 1
flex-basis  = auto

于是两者真正的差异就出来了:

flowchart LR
    A[flex: 1] --> A1[grow: 1]
    A --> A2[shrink: 1]
    A --> A3[basis: 0%]
    B[flex: auto] --> B1[grow: 1]
    B --> B2[shrink: 1]
    B --> B3[basis: auto]

关键就在 flex-basis

九、flex: 1:大家从 0 开始分

假设三个元素自身的内容尺寸分别接近:

A = 100px
B = 200px
C = 300px

容器宽度为 600px。

如果:

.item {
  flex: 1;
}

参与空间分配时,三个项目的 basis 都近似为 0,于是:

flowchart LR
    A[600px] --> B[1 : 1 : 1]
    B --> C[A 200px]
    B --> D[B 200px]
    B --> E[C 200px]

所以即使内容长度不同,三个区域依然会尽量保持相同的空间。

十、flex: auto:先尊重自己的尺寸,再分剩余空间

如果换成:

.item {
  flex: auto;
}

也就是:

flex: 1 1 auto;

此时 flex-basis: auto 会让基础尺寸参考项目自身的主轴尺寸。

仍然假设:

A = 100px
B = 200px
C = 300px

容器宽度为 900px:

基础尺寸:100 + 200 + 300 = 600px

剩余空间:900 - 600 = 300px

三个元素的 grow 都是 1,因此剩余的 300px 平均增加:

A = 200px
B = 300px
C = 400px

可以看到:

flowchart LR
    A[基础尺寸<br/>100 / 200 / 300] --> B[剩余空间 300px]
    B --> C[每项 grow = 1]
    C --> D[最终尺寸<br/>200 / 300 / 400]

所以:

flex: 1 更倾向于把项目做成相等尺寸;flex: auto 则保留项目原本的尺寸差异,再分配剩余空间。

十一、width 和 flex-basis 谁说了算?

这是实际开发中非常容易遇到的问题。

例如:

.item {
  width: 200px;
  flex-basis: 300px;
}

flex-basis 不是 auto 时,在 Flex 主轴方向上,Flex sizing 会优先使用 flex-basis 作为基础尺寸,而不是简单按照 width 来计算。

因此可以把它粗略理解成:

flowchart TD
    A[Flex Item] --> B{flex-basis}
    B -->|明确值| C[使用 flex-basis 作为主轴基础尺寸]
    B -->|auto| D[参考 width / height 等主轴尺寸]

所以如果你希望明确控制 Flex Item 在主轴上的起点,通常应该优先考虑 flex-basis

十二、为什么有时候 flex: 1 还是撑破了?

还有一个非常经典的问题:

.container {
  display: flex;
}

.left {
  flex: 1;
}

.right {
  width: 300px;
}

然后发现左侧内容很长,怎么都不愿意缩小,甚至把容器撑破。

很多时候问题并不是 flex: 1 失效,而是 Flex Item 默认存在一个基于内容的最小尺寸

这也是为什么很多实际项目里会看到:

.left {
  flex: 1;
  min-width: 0;
}

min-width: 0 的作用可以粗略理解成:

允许这个 Flex Item 在需要时真正缩到内容尺寸以下,而不是被长内容的最小尺寸卡住。

尤其是下面这种布局非常常见:

flowchart LR
    A[Flex Container] --> B[左侧 flex: 1<br/>min-width: 0]
    A --> C[右侧固定 300px]
    B --> D[长文本 / 超长内容]
    D -.可能产生最小尺寸约束.-> B

因此,遇到“flex: 1 为什么还会撑爆”的问题时,min-width: 0 值得第一时间检查。

十三、为什么 flex: 1 里面的长文字还是会把布局撑爆?

这个问题和上一节密切相关。

例如:

.parent {
  display: flex;
}

.child {
  flex: 1;
}

如果 .child 内部存在很长的不可断词文本、URL 或者某些不会自动换行的内容,那么内容本身可能形成一个较大的 min-content 尺寸。

这时候即使 flex: 1 已经参与了空间分配,最终尺寸仍然可能受到最小尺寸约束影响。

通常可以这样处理:

.child {
  flex: 1;
  min-width: 0;
}

如果是长 URL、连续英文字符等,还可以进一步考虑:

.child {
  overflow-wrap: anywhere;
}

所以这里有两个不同层面的事情:

  • flex: 1:告诉浏览器这个项目如何参与 Flex 空间分配。
  • min-width: 0:告诉浏览器不要让默认的最小尺寸约束阻止它继续缩小。

十四、顺便认识几个常见的 flex 简写

除了 flex: 1,下面几个也很常见:

写法等价理解常见用途
flex: none0 0 auto完全不参与伸缩
flex: auto1 1 auto保留自身尺寸,同时参与伸缩
flex: 11 1 0%常见的等分布局
flex: 0 auto0 1 auto不主动增长,但空间不足时可以收缩

可以把它们放到一张图里:

flowchart LR
    A[flex: 1] --> A1[1 1 0%]
    B[flex: auto] --> B1[1 1 auto]
    C[flex: none] --> C1[0 0 auto]
    D[flex: 0 auto] --> D1[0 1 auto]

实际开发中,不需要把这些值全部死记下来。真正重要的是知道它们最终影响的是三个维度:

grow / shrink / basis

十五、真正应该记住的,是这一张图

以后看到 Flex 布局,不妨在脑子里过一遍:

flowchart TD
    A[Flex Container] --> B[Flex Item]
    B --> C[确定 Flex Base Size]
    C --> D[flex-basis]
    D --> E{剩余空间}
    E -->|> 0| F[flex-grow]
    E -->|< 0| G[flex-shrink]
    F --> H[扩大 Item]
    G --> I[收缩 Item]
    H --> J[最终尺寸]
    I --> J

如果再进一步压缩成一句话,就是:

先看 basis,再看空间,空间多看 grow,空间少看 shrink。

flex: 1flex: auto 的核心区别,也终于可以归结到一个地方:

flex: 1
→ 1 1 0%
→ basis 从 0 开始

flex: auto
→ 1 1 auto
→ basis 尊重自身尺寸

所以以后再看到:

flex: 1;

不要再把它简单理解成“平均分配”。

更准确的理解应该是:

“我的基础尺寸按 0 参与 Flex 空间分配,如果有剩余空间,我按照 grow 比例拿走它。”

这才是 Flex 真正值得理解的地方。

更早的文章

Java JUC并发编程:从概念到实战的全面指南

欢迎在评论区留下您的见解~