原文:Flexbox: How Big Is That Flexible Box?,by Rachel Andrew
这是 Flexbox 系列的第三篇文章,在前两篇中,我介绍了 当我们在使用 display: flex 的时候,到底发生了什么?,探讨了 Flexbox 中的对齐。本文我们将学习去控制 Flex 项目的尺寸,以及浏览器默认是如何分配项目尺寸的。
Flex 项目的初始值
假设现在我们有一些内容不等的元素,给它们的父元素设置了 display: flex
。这些项目会显示成一行,然后从轴的起始处开始布局排列。下例中的三个项目都包含一些内容,因为空间富裕,所以内容在不折行的情况下即能正常显示。我们知道 flex-grow
初始值为 0
,因此 Flex 项目不会伸展,Flex 容器的末尾存有剩余空间。
当前存在富裕的空间供 Flex 项目排列显示
如果我修改一下,增加项目里的内容,文本开始出现折行。每个项目根据实际的内容量,都会从容器中分得一定比例的空间——拥有更多内容的 Flex 项目会分得更多的空间,这就避免了,当隔壁项目只包含很少内容(比如就一个单词)的时候,内容多的项目很窄很高。
内容多的项目会分得更多的空间
如果你经常使用 Flexbox 布局的话,这种行为是很容易发现的,你也许奇怪过,浏览器是如何给项目安排空间的。计算项目尺寸的细节在规范中有详细解释,每个实现此规范的浏览器厂商都会按照规范定义实现,计算出哪个哪个项目该分配多少多少空间。我们查阅规范就能找到这些信息。
CSS 内在尺寸和外在尺寸
当在查阅 Flexbox 规范时,很快会发现当涉及有关尺寸调节的内容时,我们会被引导查阅另一个规范——CSS Intrisnic and Extrinsic Sizing。这是因为尺寸概念并不限于 Flexbox,就好像 Flexbox 的对齐属性还可以用在其他布局上下文里是一个道理。然而,如果想知道这些概念是如何在 Flexbox 应用的,就要看 Flexbox 规范了。你发现自己要来回参考两个规范(有些麻烦),因此下面我总结了几条关键的定义给你,在余下的文章里也会使用到这里介绍到的这些定义。
首选尺寸
一个盒子的 首选尺寸(preferred size) 是通过 width
和 height
定义的,对应的逻辑别名是 inline-size
和 block-size
。就是说:
.box {
width: 500px;
}
或使用逻辑别名 inline-size
:
.box {
inline-size: 500px;
}
这里设置的样式,是想要得到一个宽度为 500 像素的盒子,或者说得到一个在内联方向上为 500 像素的盒子。
min-content 尺寸
min-content 尺寸是指盒子在不发生溢出情况下的最小尺寸。如果你的盒子里包含的是文本,那这个尺寸就是那个最长的英文单词的长度(如果都是中文的话,最小尺寸就是一个汉字)。
max-content 尺寸
max-content 是包含内容的盒子能够达到的最大尺寸。也就是,盒子里包含的内容在完全没有折行的情况下占据的尺寸,这样盒子会显示地尽可能的长。
Flex 项目的主轴尺寸
Flex 项目的 主轴尺寸(main size) 是指项目在主轴维度(main dimension)上占据的空间。在英文环境下,如果是在 row
方向上,这个主轴尺寸对应的就是宽;如果是在 column
方向上,这个主轴尺寸对应的就是高。
在主轴维度上,我们还可以通过设置 min-width
和 max-width
限制 Flex 项目能够达到的最小/最大主轴尺寸。
计算 Flex 项目的尺寸
我们已经定义了一些术语,再来看看 Flex 项目的尺寸是如何计算的。应用在项目上的 flex
属性的初始取值如下:
flex-grow: 0
flex-shrink: 1
flex-basis: auto
flex-basis
是计算最终尺寸的基础。如果我们设置了 flex-basis: 0
、flex-grow: 1
,就是说所有项目都没有起始宽度(starting width),因此 Flex 容器的空间被均等分配给每个项目了,也就是说每个项目占据一样大的空间。
See the Pen Smashing Flexbox Series 3: flex: 1 1 0; by Rachel Andrew
如果设置的是 flex-basis: auto
和 flex-grow: 1
,那么只会分配剩余空间,而且还是基于每个项目的当前内容进行分配的。
See the Pen Smashing Flexbox Series 3: flex: 1 1 auto 短文本 by Rachel Andrew
还有一种情况就是没有空间可供分配,比如说在一行里有非常多内容需要显示的话,就没有空间剩下来了。
See the Pen Smashing Flexbox Series 3: flex: 1 1 auto 长文本 by Rachel Andrew
如果能搞懂这里的 auto
含义,对理解 Flexbox 是如何计算 Flex 项目尺寸的原理至关重要。下面我们就从 auto
开始讲起。
定义 auto
CSS 中,auto 被作为一个值定义使用。这个关键字在不同的上下文中,有不同的意义,还是很值得我们进一步学习的。CSS 工作组花了大量时间来定义 auto
在不同上下文中所起的作用,可以参看 规范编辑 Fantasai 曾经做过的一个演讲。
在规范中,我们可以找到 auto
使用在 flex-basis
中的含义,上文中介绍的几个定义有助于我们理解下面这句话:
当为 Flex 项目指定
flex-basis
为auto
后,该关键字会获得主轴尺寸属性(main size property)的值,如果值是auto
的话,就使用content
作为值。
如果 flex-basis
设置为 auto
的话,Flexbox 会查找主轴尺寸属性。如果我们为 Flex 项目设置了 width
,那么这就是主轴尺寸了。下例中,所有的项目都设置了 110px
的宽度,那么这就是在 flex-basis
使用初始值 auto
情况下,实际使用的主轴尺寸。
See the Pen Smashing Flexbox Series 3: flex items with a width by Rachel Andrew
在开始的例子中,我们没有给 Flex 项目设置 width
,也就是说主轴尺寸是 auto
,我们再看下一句话“如果值是 auto
的话,就使用值 content
”。
我们现在来看下规范中是如何对 content
关键字所做的解释。这是另一个你可以为 flex-basis
使用的值(在支持的浏览器上),例如:
.item {
flex: 1 1 content;
}
规范对 cocntent
是这样定义的:
“它表示的是一个基于 Flex 项目内容的自动尺寸。通常等同于
max-content
,但在某些情况下会经过调整,比如在处理高宽比(aspect ratios)、内部尺寸限制(intrinsic sizing constraints)或者正交流(orthogonal flows)等情况下。”
我们例子里,Flex 项目里只包含文本,因此我们忽略上述里讲到的调整,把 content
看成是 max-content
。
这就解释了,为什么在存在少量文本的项目里,文本不会折行。Flex 项目是 auto-sized 的,Flexbox 查看项目的 max-content
,这些项目能够完全在容器中布局排列,布局就完成了。
还没讲完,当我们为盒子添加更多内容后,就不能保持在 max-content
的尺寸了。如果不这么做的话,项目将冲破 Flex 容器发生溢出。一旦项目填满了 Flex 容器空间,里面的内容就会折行,然后 Flex 项目基于其自身的内容,变成大小不同的尺寸。
解析弹性长度
到现在这里后,规范的解释就开始变得相当复杂了,但是,采取的步骤如下:
首先呢,将所有的 Flex 项目拼在一起跟容器的可用空间进行比较,看看比后者小还是大。
如果容器的空间比所有项目加起来使用的空间要大的话,我们关心的是 flex-grow
因子,因为我们有空间去伸展了。
第一个用例中,我们的 Flex 项目有足够的空间用来伸展
如果容器的空间比所有项目加起来使用的空间要小,我们关心的是 flex-shrink
因子,因为我们需要收缩了。
第二个用例中,Flex 项目占据的空间多,容器空间不够,就需要收缩以便适应容器空间
如果起作用的是 flex-grow
,那么它的初始值使用的是 0
,因此 Flex 项目扩大到 max-width
后,因为不允许伸展,所以在主轴上就保持这个尺寸了。
如果使用了 flex-shrink: 0
。我们会看到 Flex 项目保持在 max-content
的状态,不会收缩以适应容器的空间。
See the Pen Smashing Flexbox Series 3: flex: 0 0 auto by Rachel Andrew
在我们的例子中——使用 Flex 项目的初始值——项目可以收缩。因此,步骤继续执行,进入算法的下一个循环,在这个循环中,它计算出要分配或删除多少空间。上面我们设置了 flex-shrink: 0
,所有项目拼在一起的尺寸是大于容器的,因此我们需要从 Flex 项目上删除空间。
flex-shrink
是作用在内部基础尺寸(inner base size)上的,在我们的用例中就是指 max-content
。如果项目只根据 flex-shrink
因子移除空间,那么小的项目实际上可能消失,因为它们的所有空间都被移除了,而较大的项目仍有空间可以收缩。
这里涉及的是简化过之后的规范说法,我还没有研究过一些更边缘的用例场景。如果无须追求像素级别的 Flexbox 布局的话,记住下面两条在大多数情况下是有用的:
如果是从 auto
开始伸展的,那么项目的 flex-basis
将被视为项目的宽度或高度(在指定 width
或 height
的情况下),或者 max-content
(未指定 width
或 height
的情况)。然后,使用这个尺寸作为起始点,根据 flex-grow
因子按比例分配空间。
如果是从 auto
开始收缩的,那么 flex-basis
将被视为项目的宽度或高度,或是 max-content
值。然后,将基于 flex-basis
、根据 flex-shrink
因子,按比例删除空间,此删除操作是基于项目的 max-content
。
控制伸展和收缩
本文中我花了大把笔墨描述 Flexbox 中项目尺寸的分配原理。当然,你还可以使用 flex
属性获得对 Flex 项目更大程度的控制,特别是在你搞懂了这背后的原理之后。
通过手动设置 flex-basis
,或者为项目指定尺寸(被 flex-basis
所用),让我们获得比浏览器渲染算法更高的优先级,让 Flexbox 按照我们预期的方式伸展或收缩到特定尺寸。当然也可以通过设置 flex-grow
和 flex-shrink
为 0
关闭项目的伸展和收缩效果。在伸展和收缩功能的使用过程中,一定程度上会让你感知到你是不是正确使用了此布局方法。当你试图想在二维维度上对齐 Flex 项目的时候,说明你更需要的可能是 Grid 布局。
Bebug 尺寸相关的问题
如果你的 Flex 项目的尺寸呈现不符合心意的话,通常可能是因为 flex-basis
auto
了,然后从哪配得到了一个宽度,被 flex-basis
拿来使用了。可以使用 DevTools 检查尺寸的来源。或者你可以设置 flex-basis
为 0
,将 Flex 项目的基础尺寸看做 0
。即使最后没有得到我们想要的结果,这也将有助于确定使用的 flex-basis
是导致此问题发生的原因。
Flex 间隙
Flexbox 的一个非常受欢迎的特性是能够指定 Flex 项之间的间隙或沟槽,就像我们可以在网格布局和多列布局中指定间隙一样。这块功能是在 Box Alignment 中指定的。Firefox 在 Firefox 63 中为 Flexbox 提供 gap 属性。下面的示例需要在 Firefox 63+ 版本中看到。
See the Pen Smashing Flexbox Series 3: flex-gaps by Rachel Andrew
类似于 Grid 布局,在给 Flex 项目分配空间之前,会先去掉 gap 之后再进行分配的。
总结
在本文中,我试图解释 Flexbox 如何计算 Flex 项目尺寸。可能有点学院派,但是搞懂了这块的话,在你使用 Flexbox 进行页面布局的时候能节省下不少的时间。我发现在处理一堆不同尺寸的项目上,Flexbox 默认的布局方式是比较合理的。内容多的项目会分配更多的空间,如果你并不需要这个行为,可以通过设置 flex-basis
来控制这个行为。
(完)