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

scott0130 8年前

来自: http://jinlong.github.io/2016/02/04/flex-grow-is-weird/

当我刚接触 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 元素,一个挨一个的横向排列。

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

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

计算结果

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

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

父容器宽: 900px

section 宽: 99px

aside 宽: 623px

flex-grow 总值: 3

1. 首先计算剩余空间

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

900 - 99 - 623 = 178
</div>

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

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

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

178 / 3 = 59.33
</div>

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

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

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

99 + (2 * 59.33) = 217.66 (≈218px)
</div>

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

623 + (1 * 59.33) = 682.33 (≈682px)
</div>

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

so easy,不是吗?

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

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

父容器宽: 900px

section 宽: 0px

aside 宽: 0px

flex-grow 总值: 3

1. 计算剩余空间

900 - 0 - 0 = 900
</div>

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

900 / 3 = 300
</div>

3. 分配剩余空间

0 + (2 * 300) = 600    0 + (1 * 300) = 300
</div>

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

flex-grow 和 flex-basis

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

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

当然可以。有个 flex-basis 属性,它可以定义元素的初始宽度。如果 flex-basis 和 flex-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. 计算剩余空间

900 - 400 - 200 = 300
</div>

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

300 / 3 = 100
</div>

3. 分配剩余空间

400 + (2 * 100) = 600    200 + (1 * 100) = 300
</div>

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

与盒模型结合

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

唯一需要注意的是,使用 box-sizing 的话, flex-basis 跟 width 属性表现相似。 如果 box-sizing 属性改变 ,计算结果也会变化。如果 box-sizing 设置为 border-box ,计算时只需用到 flex-basis 和 margin 值,因为 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 的话,应该这么定义:

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

深入学习 Flexbox

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

总结经验教训

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

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

</div>