当我刚接触 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. 首先计算剩余空间
父容器宽减去每一个子元素的初始宽度。
|
|
父容器宽 - section 宽 - aside 宽 = 剩余空间
2. 然后计算 flex-grow 的1份是多宽
既然有了剩余空间,我们还需要确定把它切成几份。重要的是,我们不按元素的个数切分剩余空间,而是按 flex-grow 总值,所以这里是 3 (flex-grow: 2
+ flex-grow: 1
)
|
|
剩余空间 / flex-grow 总值 = “1份 flex-grow 的宽”
3. 最终所有的元素瓜分剩余空间
依据它们的 flex-grow 值,section 需要 2 份(2 59.33),aside 需要 1 份(1 59.33)。这个数字再与每个元素的初始宽度相加。
|
|
初始 section 宽度 + (section 的 flex-grow 值 * “1 份 flex-grow 的宽”) = 新的宽度
|
|
初始 aside 宽度 + (aside 的 flex-grow 值 * “1 份 flex-grow 的宽”) = 新的宽度
so easy,不是吗?
好吧,那为什么第一个例子正常呢?
我们按已有的公式,算下第一个例子 。
父容器宽:900px
section 宽:0px
aside 宽:0px
flex-grow 总值:3
1. 计算剩余空间
|
|
2. 计算 flex-grow 的1份是多宽
|
|
3. 分配剩余空间
|
|
如果每个元素的宽是 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. 计算剩余空间
|
|
2. 计算 flex-grow 的1份是多宽
|
|
3. 分配剩余空间
|
|
仅仅是为了完整性,我才用到 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
的话,应该这么定义:
|
|
深入学习 Flexbox
如果你想深入学习 Flexbox,可以看看这些不错的资源:
- A Complete Guide to Flexbox by Chris Coyier
- Flexbox adventures by Chris Wright
- Flexbox Froggy by Thomas Park
- What the Flexbox? by Wes Bos
- flexboxin5
- Flexbox Tester by Mike Riethmuller
总结经验教训
flex-grow
不易理解吗?也不全是。我们只需理解它如何工作,它做了什么。如果一个元素设置 flex-grow
为 3
,并不代表它是 flex-grow
为 1
的元素的3倍大,准确含义是:它的初始宽度可以增加的像素值是另一个元素的3倍。
我通过两个空元素 测试 flex-grow ,得到的结论跟真实情况 完全不符。应该在尽可能真实的环境中验证新事物,这样才能得到最切合实际的结论。