flex-grow 不易理解,难道不是吗?

原文:‘flex-grow’ is weird. Or is it?
翻译:涂鸦码龙

当我刚接触 flex-grow 时,为了探寻它的工作原理,做了一个简单的例子

我以为理解的挺透彻了,但是当我把它应用到同事的网站上时,效果跟我想象的完全不同。无论怎么改,布局都无法像我的demo那样展示。这时我才意识到,我并没有完全掌握 flex-grow

flex-grow 为何不正常

在我深入剖析 flex-grow 的功能之前,我想解释一下我起初犯了什么错。

我认为所有 flex 元素的 flex-grow 如果设置为 1 ,它们将一样宽。如果某一项的 flex-grow 设置为 2 ,它将是其它元素的二倍宽。

一切听起来顺理成章。我上面的例子 貌似也印证了这点。父元素是900px宽,flex-grow: 2 的 section 元素计算后是600px宽,flex-grow: 1 的 aside 元素计算后是300px宽。

如你所见,它在这个例子中展现的近乎完美,可是在真实的例子中却不尽人意,即使我们用了完全相同的 CSS。事实证明,问题不在 CSS,而在于内容(或者说缺乏内容)。我的测试用例只用了两个空元素,无法展示这个属性最重要的细节。

flex-grow 到底如何工作

啰嗦了半天,我终于要解释 flex-grow 没有尽如人意的原因了。

为了阐明原因,我又搞了个栗子 ,所有的设置跟第一个栗子 完全一致,只不过 section 和 aside 元素不再是空的。看吧,两个元素的比例不再是 2 : 1,flex-grow 为 1 的元素的确比 flex-grow 为 2 的元素宽不少呐。

解析

如果给父元素设置了 display: flex; ,子元素仅仅是水平排列,没有其它效果了。如果没有足够的空间,它们会收缩一些尺寸。另一方面,如果有足够的空间,它们也不会扩展,因为 Flexbox 希望我们自己定义扩展多少。flex-grow 恰恰用来定义剩余空间如何分配,每一项分享多大宽度。

换言之:

flex 容器为它的子元素分配剩余空间(它们的扩展系数是成比例的),从而填满整个容器,或者收缩元素(它们的收缩系数也是成比例的),从而阻止溢出。

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

证明

如果我们可视化一把,概念就很清晰明了了。

首先,我们给父元素设置了 display: flex,然后它的子元素成了 flex 元素,一个挨一个的横向排列。

附图1

下一步,我们要决定每个元素能获得多少剩余空间。先前的例子中,第一个元素获得了 2/3 的剩余空间(flex-grow: 2),第二个元素获得了 1/3 的剩余空间(flex-grow: 1)。想知道 flex-grow 总共的值是多少,看看剩余空间被分成了几份吧。

附图2

最终我们得出了可分配的块数,根据 flex-grow 的值,每个元素可以获得适当的块数。

附图3

计算结果

理论和视觉展示都不错,让我们为上面例子 做点算术吧。

我们需要4组数字:父容器宽度, section 和 aside 元素的初始宽度,以及 flex-grow 的总值。

父容器宽:900px
section 宽:99px
aside 宽:623px
flex-grow 总值:3

1. 首先计算剩余空间

父容器宽减去每一个子元素的初始宽度。

1
900 - 99 - 623 = 178

父容器宽 - section 宽 - aside 宽 = 剩余空间

2. 然后计算 flex-grow 的1份是多宽

既然有了剩余空间,我们还需要确定把它切成几份。重要的是,我们不按元素的个数切分剩余空间,而是按 flex-grow 总值,所以这里是 3 (flex-grow: 2 + flex-grow: 1

1
178 / 3 = 59.33

剩余空间 / flex-grow 总值 = “1份 flex-grow 的宽”

3. 最终所有的元素瓜分剩余空间

依据它们的 flex-grow 值,section 需要 2 份(2 59.33),aside 需要 1 份(1 59.33)。这个数字再与每个元素的初始宽度相加。

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

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

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

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

so easy,不是吗?

好吧,那为什么第一个例子正常呢?

我们按已有的公式,算下第一个例子

父容器宽:900px
section 宽:0px
aside 宽:0px
flex-grow 总值:3

1. 计算剩余空间

1
900 - 0 - 0 = 900

2. 计算 flex-grow 的1份是多宽

1
900 / 3 = 300

3. 分配剩余空间

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

如果每个元素的宽是 0,剩余空间等于父容器的宽度,因此,看起来像是 flex-grow 按比例划分了父容器的宽度。

flex-grow 和 flex-basis

快速回顾一下:剩余空间被 flex-grow 的总值划分,由此产生的商,乘以各自的 flex-grow 值,结果再加上每个元素的初始宽度。

但是如果没有剩余空间或者不想依赖元素的初始宽度,我们可以设置它吗?还能用 flex-grow 吗?

当然可以。有个 flex-basis 属性,它可以定义元素的初始宽度。如果 flex-basisflex-grow 一起设置,宽度的计算方式得变一下。

<‘flex-basis’> :按 flex 因子分配剩余空间之前,每个 flex 元素的最初主要尺寸。

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. 计算剩余空间

1
900 - 400 - 200 = 300

2. 计算 flex-grow 的1份是多宽

1
300 / 3 = 100

3. 分配剩余空间

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

仅仅是为了完整性,我才用到 px 值,用百分比的话当然也没问题

与盒模型结合

为了覆盖所有情况,如果我们加上 padding 和 margin 看看会发生什么,啥也没发生 ,第一步计算的时候,只要减去 margin 就好了。

唯一需要注意的是,使用 box-sizing 的话, flex-basiswidth 属性表现相似。如果 box-sizing 属性改变,计算结果也会变化。如果 box-sizing 设置为 border-box ,计算时只需用到 flex-basismargin 值,因为 padding 已经包含到宽度里面了。

更多实用的例子

好吧,算术是玩够了。我再展示一些项目中合理使用 flex-grow 的例子吧。

不限宽度:[ x ]%

实际应用中,剩余空间是自动分配的,如果想让子元素填满父容器的话,我们没必要再考虑宽度值。

See the Pen flex-grow by Manuel Matuzovic (@matuzo) on CodePen.

固定宽度的 “圣杯” 3列流式布局

固定加自适应宽度的混合布局,可以用浮动实现,但是既不简单直观,又不灵活。Flexbox 实现的话,加点儿 flex-grow 和 flex-basis 魔法,简直小菜一碟。

See the Pen Layout using fluid and fixed widths by Manuel Matuzovic (@matuzo) on CodePen.

使用任何元素填满剩余空间

比如,一个 label 元素后面紧跟着输入框,想让输入框填满剩余空间,不再需要丑陋的 hacks 。

See the Pen Filling up the remaining space in a form by Manuel Matuzovic (@matuzo) on CodePen.

Philip Waltons 的 Solved by Flexbox 一文可以找到更多示例。

听听标准规范怎么说

根据标准,使用 flex 的简写,比直接用 flex-grow 更好。

Authors are encouraged to control flexibility using the flex shorthand rather than flex-grow directly, as the shorthand correctly resets any unspecified components to accommodate common uses.

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

但是小心!如果仅仅使用 flex: 1; ,以上某些例子可能无法正常展示。

我们的例子要用 flex 的话,应该这么定义:

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

深入学习 Flexbox

如果你想深入学习 Flexbox,可以看看这些不错的资源:

总结经验教训

flex-grow 不易理解吗?也不全是。我们只需理解它如何工作,它做了什么。如果一个元素设置 flex-grow3 ,并不代表它是 flex-grow1 的元素的3倍大,准确含义是:它的初始宽度可以增加的像素值是另一个元素的3倍。

我通过两个空元素 测试 flex-grow ,得到的结论跟真实情况 完全不符。应该在尽可能真实的环境中验证新事物,这样才能得到最切合实际的结论。