`flex-grow` 很奇怪。还是?

Avatar of Manuel Matuzovic
Manuel Matuzovic

DigitalOcean 提供适合您旅程各个阶段的云产品。从 200 美元的免费积分 开始!

以下是 Manuel Matuzovic 的客座文章。它说明了 flex-grow 的工作原理,包括奇怪的怪癖。然后,他介绍了几个使用 `flex-grow` 和 `flex-basis` 实现常见布局模式的示例。

当我了解到 `flex-grow` 时,我制作了一个 简单的演示 来了解它的作用以及工作原理。
我以为我弄清楚了所有东西,但当我尝试将其应用于我同事最近制作的一个网站时,什么都没有按预期工作。无论我们做什么,布局看起来都不像我的演示,而且也不像我的演示那样工作。这让我开始思考,并开始怀疑我是否了解 `flex-grow` 的全部内容。

flex-grow 如何工作

在我们深入探讨 `flex-grow` 的功能之前,我想向你解释一下我一开始错在哪里。

我以为所有 `flex-grow` 设置为 `1` 的弹性项目都将具有相等的宽度。如果其中一项的 `flex-grow` 设置为 `2`,那么该项将是所有其他项的两倍大小。

这听起来很棒。似乎这正是 我之前提到的笔 中发生的事情。父元素的宽度为 900px,`flex-grow: 2` 的部分的计算宽度为 600px,而 `flex-grow: 1` 的 aside 元素的计算宽度为 300px。

如您所见,在这个演示中一切都完美地运行,但在实际示例中它根本不起作用,即使我们使用了完全相同的 CSS。事实证明,问题不在于 CSS,而在于内容(或缺乏内容)。我制作的演示之所以有效,是因为只使用两个空元素创建了一个测试用例,它过于简单,无法描述该属性的重要细节。

`flex-grow` 的真实工作原理

我刚刚描述了 `flex-grow` 如何工作,但我向你展示了一个实际上确实按照我的说法工作的演示(在这篇文章的后面,我将解释原因。)。

为了澄清,让我向你展示 另一支笔。我们有与 第一支笔 相同的设置,但这一次 section 和 aside 元素不是空的。现在比例不再是 2:1,并且 `flex-grow` 设置为 1 的元素实际上比 `flex-grow` 设置为 2 的元素更大。

解释

如果我们将 `display: flex;` 应用于父元素,并且不更改任何其他内容,则子元素将水平堆叠,无论如何。如果没有足够的 space,它们的大小会缩小。另一方面,如果 space 多余,它们不会增长,因为 Flexbox 希望我们定义它们应该增长多少。因此,与其告诉浏览器元素应该多宽,不如flex-grow 确定剩余 space 如何在弹性项目之间分配,以及每个项目获得多少份额。

换句话说

弹性容器将其剩余 space 分配给它的项目(与其 flex-grow 因素成比例),以填充容器,或者缩小它们(与其 flex-shrink 因素成比例),以防止溢出。

https://drafts.csswg.org/css-flexbox/#flexibility

演示

如果我们可视化它,这个概念就更容易理解。

首先,我们将父元素的 `display` 属性设置为 `flex`,通过这样做,我们的子元素将成为弹性项目,并水平地彼此相邻排列。

接下来,我们决定每个元素获得多少份额的额外 space。在我们之前的示例中,第一个元素获得了 2/3 的剩余 space (`flex-grow: 2`),第二个元素获得了 1/3 (`flex-grow: 1`)。通过了解我们总共有多少个 flex-grow 值,我们就知道要将剩余 space 除以多少。

最后,我们得到可分配的块数。每个元素根据其 `flex-grow` 值获得适当数量的块。

计算

理论和可视化表示很好,但让我们实际操作一下,并针对 上面的示例 进行数学运算。

为了进行计算,我们需要 4 个数字:父元素的宽度、section 和 aside 元素的初始宽度以及我们将使用的总 flex-grow 值。

父元素宽度:**900px**
section 宽度:**99px**
aside 宽度:**623px**
总 flex-grow 值:**3**

1. 首先,我们必须计算剩余 space

这很简单。我们将父元素的宽度减去所有子元素的初始宽度总和。

900 - 99 - 623 = 178

父元素宽度 – section 的初始宽度 – aside 的初始宽度 = 剩余 space

2. 接下来,我们必须确定一个 `flex-grow` 的值

现在我们有了剩余 space,我们需要确定要将其切割成多少片。这里重要的是,我们不是将剩余 space 除以元素数量,而是除以总 flex-grow 值数量。因此,在本例中,它是 3 (`flex-grow: 2` + `flex-grow: 1`)

178 / 3 = 59.33

剩余 space / 总 flex-grow 值 = “一个 flex-grow”

3. 最后,切片后的剩余 space 将分配给所有元素

根据它们的 flex-grow 值,section 获得 2 片 (2 * 59.33),aside 获得 1 片 (1 * 59.33)。这些数字将添加到每个元素的初始宽度。

99 + (2 * 59.33) = 217.66 (≈218px)

section 的初始宽度 + (section 的 flex-grow 值 * “一个 flex-grow”) = 新宽度

以及

623 + (1 * 59.33) = 682.33 (≈682px)

aside 的初始宽度 + (aside 的 flex-grow 值 * “一个 flex-grow”) = 新宽度

是不是很简单?

好吧,但为什么第一个演示会起作用?

我们有公式,现在让我们找出为什么我们实际上在 第一支笔 中得到了我们想要的结果,即使我们不知道自己在做什么。

父元素宽度:**900px**
section 宽度:**0px**
aside 宽度:**0px**
总 flex-grow 值:**3**

1. 计算剩余 space

900 - 0 - 0 = 900

2. 确定一个flex-grow 的值

900 / 3 = 300

3. 分配切片后的剩余 space

0 + (2 * 300) = 600  
0 + (1 * 300) = 300

如果每个元素的宽度为 0,那么剩余 space 等于父元素的实际宽度,因此看起来 `flex-grow` 将父元素的宽度按比例分成几部分。

`flex-grow` 和 `flex-basis`

快速回顾一下:`flex-grow` 将获取剩余 space 并将其除以 flex-grow 值的总数量。得到的商乘以相应的 `flex-grow` 值,结果将添加到每个子元素的初始宽度。

但是如果没有任何剩余 space,或者我们不想依赖于元素的初始宽度,而想要依赖于我们设置的值,我们还能使用 `flex-grow` 吗?

当然可以。有一个名为 `flex-basis` 的属性,它定义了元素的初始大小。如果将 `flex-basis` 与 `flex-grow` 结合使用,则宽度计算方式会发生变化。

<‘flex-basis’> 此组件设置 `flex-basis` 简写形式,并指定 flex 基准:弹性项目的初始主尺寸,在根据 flex 因素分配剩余 space 之前。

https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis

如果我们将 `flex-basis` 应用于元素,最大的区别是我们不再在计算中使用它的初始宽度,而是使用它的 `flex-basis` 属性的值。

我修改了之前的例子,为每个元素添加了flex-basis这里就是代码

父元素宽度:**900px**
section 宽度:400px (flex-basis 值)
aside 宽度:200px (flex-basis 值)
总 flex-grow 值:**3**

1. 计算剩余 space

900 - 400 - 200 = 300

2. 确定一个 flex-grow 的值

300 / 3 = 100

3. 分配切片后的剩余 space

400 + (2 * 100) = 600  
200 + (1 * 100) = 300

为了完整性,你并不需要使用像素值,并希望得到最好的效果, 百分比也同样有效

处理盒模型

为了涵盖所有内容,让我们看看如果我们添加填充和边距会发生什么。 实际上并没有什么特别的。在计算的第一步,你只需要记住也要减去边距。

需要注意的是,在box-sizing方面,flex-basis的行为类似于width属性。这意味着计算以及结果会发生变化 如果box-sizing属性发生变化。如果将box-sizing设置为border-box,你只需要在计算中使用flex-basismargin值,因为padding已经包含在宽度中。

一些有用的例子

好了,关于数学就说到这里。让我给你一些关于如何在你的项目中有效地建立flex-grow的例子。

不再使用width: [ x ]%

由于剩余的空间会被自动分配,如果我们想要子元素填充父元素,我们不再需要考虑宽度值。

在 CodePen 上查看 Manuel Matuzovic (@matuzo) 的代码 QyWEBb

使用像素宽度的“圣杯”三栏液体布局

使用浮动可以将固定宽度和流体宽度混合在列布局中,但这既不直观也不容易也不灵活。当然,使用 Flexbox 和一些flex-growflex-basis魔法,这就像切蛋糕一样简单。

在 CodePen 上查看 Manuel Matuzovic (@matuzo) 的代码 jbRjMG

用任何元素填充剩余空间

例如,如果你有一个输入字段在标签旁边,并且你想要输入字段填充剩余的空间,你不再需要使用丑陋的技巧。

在 CodePen 上查看 Manuel Matuzovic (@matuzo) 的代码 eJYdWV

你可以在 Philip Walton 的 Solved by Flexbox 中找到更多示例。

关注规范

根据规范,我们应该使用flex简写而不是直接使用flex-grow

鼓励作者使用flex简写来控制灵活性,而不是直接使用flex-grow,因为简写会正确重置任何未指定的组件以适应常用情况。

https://drafts.csswg.org/css-flexbox/#flex-grow-property

但要小心!如果你只是使用flex: 1;,上面的一些例子将不再起作用,因为为常用情况设置的值并不等于默认值,因此会干扰我们的需求。

如果你想在我们的用例中使用flex,你应该这样定义它

flex: 2 1 auto;  /* (<flex-grow> | <flex-shrink> | <flex-basis>) */

学习 Flexbox

如果你想了解更多关于 Flexbox 的知识,请查看以下优秀的资源

总结和经验教训

flex-grow奇怪吗?不,一点也不。我们只需要理解它是如何工作的以及它做了什么。如果一个元素的flex-grow设置为3,这并不意味着它比flex-grow设置为1的元素大3倍,而是意味着它比另一个元素多增加了3倍的像素到其初始宽度。

我在用 两个空元素 测试flex-grow时吸取了教训,这让我完全不同地理解了该属性 实际上做了什么,这当然导致了错误的结论。你应该在一个尽可能现实的环境中检查新事物,以获得对其真正工作方式和行为的最佳印象。