以下是 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-basis
和margin
值,因为padding
已经包含在宽度中。
一些有用的例子
好了,关于数学就说到这里。让我给你一些关于如何在你的项目中有效地建立flex-grow
的例子。
width: [ x ]%
不再使用由于剩余的空间会被自动分配,如果我们想要子元素填充父元素,我们不再需要考虑宽度值。
在 CodePen 上查看 Manuel Matuzovic (@matuzo) 的代码 QyWEBb。
使用像素宽度的“圣杯”三栏液体布局
使用浮动可以将固定宽度和流体宽度混合在列布局中,但这既不直观也不容易也不灵活。当然,使用 Flexbox 和一些flex-grow
和flex-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 的知识,请查看以下优秀的资源
- Flexbox 完整指南 by Chris Coyier
- Flexbox 冒险之旅 by Chris Wright
- Flexbox 青蛙 by Thomas Park
- Flexbox 是什么? by Wes Bos
- flexboxin5
- Flexbox 测试器 by Mike Riethmuller
总结和经验教训
flex-grow
奇怪吗?不,一点也不。我们只需要理解它是如何工作的以及它做了什么。如果一个元素的flex-grow
设置为3
,这并不意味着它比flex-grow
设置为1
的元素大3
倍,而是意味着它比另一个元素多增加了3
倍的像素到其初始宽度。
我在用 两个空元素 测试flex-grow
时吸取了教训,这让我完全不同地理解了该属性 实际上做了什么,这当然导致了错误的结论。你应该在一个尽可能现实的环境中检查新事物,以获得对其真正工作方式和行为的最佳印象。
感谢分享,使用 flex CSS 非常诱人,但它是否可以保证在所有浏览器中得到支持?
所有 hack$ 是否都有一个终点?;)
多年来,所有现代浏览器都支持它。但这并不意味着不再需要 hack,因为 flexbox 可能有相当多的错误。
不用谢!如果你逐步增强你的网站并使用 Modernizr 进行功能检测,我会说你可以开始了 (https://caniuse.cn/#search=Flexbox)。使用 当前版本的 Modernizr,你可以检测到对当前 Flexbox 实现、旧版 Flexbox 以及缺少对 flex-wrap 支持的 Flexbox 的支持。
感谢这篇文章,Manuel!
学习 Flexbox 的另一个好方法是阅读 Landon Schopp 的电子书 Unraveling Flexbox。
不用谢!谢谢,看起来很有希望,我会去看一看。
很棒的文章,谢谢!
谢谢,我很感谢!
嗨,
文章解释得很好,
你可能需要更正的是,你在你的圣杯布局中犯了一个和你最初类似的错误(顺便说一下,这是 CSS Tricks 第二次弄错了 ;)),你没有在你的 section 元素中添加足够的内容来查看完整的效果。
你会发现,你的 150px 侧边栏不会保持在 150px,而是会在中间栏有更多内容时缩小。你需要将 flex-shrink 设置为零。
”’aside {
flex-basis: 150px;
flex-shrink: 0;
}
”’
除此之外,干得不错。
你抓住了我 ;) 谢谢你提供反馈!我已经更新了代码。
嗨,
在三栏液体布局示例中,使用“flex-basis: 150px”和简单地使用“width: 150px”之间有什么区别?
谢谢,顺便说一下,这是一篇很棒的文章;)
Flex-basis 始终适用于 flex-container 的主轴,而 width 仅适用于元素的水平轴。使用 “flex-direction: column;” 属性可能会看到区别。 http://codepen.io/anon/pen/zroYEP?editors=110
如果设置 “flex-basis: 0”,则内容应该没有意思,flexbox 只是使用 flex-grow 或 flex-shrink 属性来计算宽度,就像你在第一个示例中想要的那样。
所以尝试在第二个示例中使用更多内容在 aside 中使用以下代码,结果应该与示例 1 相同
这里有一个示例
http://codepen.io/oliver41/pen/MKbwyd
是的,当然。它基本上与第一个示例(在“好吧,但是为什么第一个演示有效?”)中的解释相同,但明确设置了宽度。感谢你指出来。
正如你的演示中所示,你实际上可以只写
flex: 2
和flex: 1
,因为简写形式 将flex-basis
设置为零。不,我们谈论的是第二个示例,其中有内容。你需要将 flex-basis 设置为 0px,否则它将被设置为 auto,而 aside 将占据更多宽度,正如你可以在你的第二个示例中看到的那样,正如你在文章中解释的那样。
我们这里没有计算任何东西,也没有设置任何宽度。只是使用 flex-grow 和 flex-shrink 属性的比率(与你在示例 1 中做的一样,并且尝试在示例 2 中只设置 flex-basis: 0px,而内容并不重要)
是的,我明白,我们在这里谈论的是同一件事。;) 我已经提供了解释,并给你提供了一个可能的解决方案。该计算解释了我的第一个示例以及你的使用
flex-basis: 0;
的示例。顺便说一句,因为我们在这个示例中正在处理 x 轴,所以我们实际上使用
flex-basis
设置了(初始)宽度。> 此组件设置 flex-basis 长手写,并指定 flex 基准
flex 项目的初始主要尺寸,在根据 flex 因子分配剩余空间之前。
https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis
很棒的文章,伙计!我们现在在一些大型应用程序中使用 flexbox,这篇文章给了我一些很好的见解,因为我在 flex-grow 上遇到了同样的问题,但直到现在还不确定它是如何工作的。
谢谢!很高兴听到这些。
谢谢!我也花了很长时间才意识到所有这些事情。
在实践中,我也看到我经常使用两种情况
感谢你分享!
很棒的文章。谢谢 Manuel!
谢谢,Pawel!
太棒了!解释得很好。这帮助很大,谢谢。
谢谢,很高兴听到这个!
很棒的文章。如果你不小心,flex grow、shrink 和 basis 真的会让你绊倒。理解 flexbox 的关键是理解轴、行以及 flex 项目如何适应,并尝试智能地使用空白空间。Flexbox 并不总是按你预期的方式工作,但通常是轴或空白空间导致了混淆。使用 flexbox 一年多了,但我仍在学习。像你这样的文章非常有帮助;)
谢谢!我们都在同一条船上。Flexbox 提供了如此多的新属性以及组合这些属性的可能性,以至于在某些时候它会让人望而生畏。我们只需要继续使用这些新属性并学习它们。;)
解释得非常棒!
我最近一直在大量使用 flexbox,当我看到你的空间划分图时,我终于理解了 flex-grow。
完全理解这一点,让你可以在更多情况下更自信地使用它
感谢你的积极反馈。我很高兴我的文章能帮助到你!