[译]这个弹性盒子有多大?

933 阅读11分钟

原文:Flexbox: How Big Is That Flexible Box?,by Rachel Andrew

摄影 | Free-Photos

这是 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) 是通过 widthheight 定义的,对应的逻辑别名是 inline-sizeblock-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-widthmax-width 限制 Flex 项目能够达到的最小/最大主轴尺寸。

计算 Flex 项目的尺寸

我们已经定义了一些术语,再来看看 Flex 项目的尺寸是如何计算的。应用在项目上的 flex 属性的初始取值如下:

  • flex-grow: 0
  • flex-shrink: 1
  • flex-basis: auto

flex-basis 是计算最终尺寸的基础。如果我们设置了 flex-basis: 0flex-grow: 1,就是说所有项目都没有起始宽度(starting width),因此 Flex 容器的空间被均等分配给每个项目了,也就是说每个项目占据一样大的空间。

See the Pen Smashing Flexbox Series 3: flex: 1 1 0; by Rachel Andrew

如果设置的是 flex-basis: autoflex-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-basisauto 后,该关键字会获得主轴尺寸属性(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 将被视为项目的宽度或高度(在指定 widthheight 的情况下),或者 max-content(未指定 widthheight 的情况)。然后,使用这个尺寸作为起始点,根据 flex-grow 因子按比例分配空间。

如果是从 auto 开始收缩的,那么 flex-basis 将被视为项目的宽度或高度,或是 max-content 值。然后,将基于 flex-basis、根据 flex-shrink 因子,按比例删除空间,此删除操作是基于项目的 max-content

控制伸展和收缩

本文中我花了大把笔墨描述 Flexbox 中项目尺寸的分配原理。当然,你还可以使用 flex 属性获得对 Flex 项目更大程度的控制,特别是在你搞懂了这背后的原理之后。

通过手动设置 flex-basis,或者为项目指定尺寸(被 flex-basis 所用),让我们获得比浏览器渲染算法更高的优先级,让 Flexbox 按照我们预期的方式伸展或收缩到特定尺寸。当然也可以通过设置 flex-growflex-shrink0 关闭项目的伸展和收缩效果。在伸展和收缩功能的使用过程中,一定程度上会让你感知到你是不是正确使用了此布局方法。当你试图想在二维维度上对齐 Flex 项目的时候,说明你更需要的可能是 Grid 布局。

Bebug 尺寸相关的问题

如果你的 Flex 项目的尺寸呈现不符合心意的话,通常可能是因为 flex-basis auto 了,然后从哪配得到了一个宽度,被 flex-basis 拿来使用了。可以使用 DevTools 检查尺寸的来源。或者你可以设置 flex-basis0,将 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 来控制这个行为。

(完)