阅读 1580

[译]当我们在使用 display: flex 的时候,到底发生了什么?

原文:What Happens When You Create A Flexbox Flex Container?,by Rachel Andrew

按我想法的话,CSS 网格布局(grid)和弹性布局(Flexbox)应该同时出现才对,这样网页布局方案就变得完整了。事实是,弹性布局先出现,因为使用弹性布局创建类网格(grid-type)系统比使用浮动更加便捷,于是我们便得到了许多基于 Flexbox 的网格系统。实际上,Flexbox 的优势并不是用来创建网格系统,这也是为什么有时我们在用它创建网格系统时,感觉很费劲的原因。

我准备开一个小系列的文章,花点时间解密一下 Flexbox——就像过去我在讲解 Grid 一样。我们将看到 Flexbox 的设计目的,它真正做得好的地方,以及为什么我们不选择它作为布局方法。本文中将会介绍当我们在使用 display: flex 的时候,到底发生了什么?

Flex 容器

为了使用弹性布局,我们需要先有一个元素充当 Flex 容器的角色。容器使用 display: flex 声明:

📷See the Pen Smashing Flexbox Series 1: display: flex; by Rachel Andrew

display: flex 到底做了什么呢?在 Display Module Level 3 规范中,定义每 display 属性值由两部分组成:内部显示模型(inner display model)和外部显示模型(outer display model)。我们使用 display: flex 的时候,实际定义的是 display: block flex。Flex 容器的外部显示类型是 block,在文档流(normal flow)中表现为块级元素(block level element),内部显示类型是 flex,所以容器的直接子元素将参与弹性布局。

当然,我们还可以使用 display: inline-flex 定义 Flex 容器,与上面声明类似,它实际定义的是 display: inline flex。Flex 容器表现的像个行内元素,其直接子元素将参与弹性布局。

📷See the Pen Smashing Flexbox Series 1: display: inline-flex; by Rachel Andrew

理解了元素的外部显示模型和内部显示模型,对理解元素及其子元素在页面中的表现行为非常有帮助。你可以将这种思路带入到任意其他类型的盒子中:这个元素的表现特征为何?它的子元素呢?答案是跟外部显示模型和内部显示模型有关。

行与列

定义好 Flex 容器后,一些初始值就开始起作用了。在我们没有设置任何其他属性的情况下,所有的 Flex 项目 会排列为一行。之所以如此,是因为 flex-direction 的初始值是 row

flex-direction 属性设置的是主轴(main axis)的方向,取值除了 row 之外,还包括:

  • column
  • row-reverse
  • column-reverse

这些排列在一行的 Flex 项目,始于内联维度(inline dimension)的起始边缘(start edge)、按照在源码中出现的结构顺序,依次排列。在规范中,这个“起始边缘”被称为 main-start

main-start 位于内联维度的起始端

如果使用的是 column,则 Flex 项目从块维度(block dimension)起始边缘开始排列,从而形成一列。

main-start 位于块维度的起始端

如果使用的是 row-reverse,则 main-startmain-end 的位置就调换了。因此,Flex 项目会一个个按照与之前相反的顺序排列。

main-start 位于内联维度的终端

column-reverse 的作用于此类似。需要知道的是,这些值没有“改变 Flex 项目的顺序”,顺序变了只是表征而已,改变的其实是 Flex 项目从哪开始布局,也就是说改变的是 main-start 的位置。因此,Flex 项目按照反序显示,因为它们是沿着容器 的另一边开始布局的。

还有一个比较重要的点,就是 Flex 项目的排列顺序上的不同只是纯视觉上的。我们只是要求 Flex 项目从结束边缘(end edge)开始显示,对于屏幕阅读器(screen reader)或是当你按 Tab 键切换元素的时候,结果顺序还是在源码中出现的顺序。

上面多此重复按下 Tab 键,可以观察按钮被 focus 的顺序与在源码中出现的顺序一样,与显示顺序无关。

Flexbox 中的两根轴

我们已经讲了弹性布局里一个非常重要的特性:主轴方向能从行切换到列。理解这种轴切换,能使我们更好地理解网格布局中对齐的工作原理。网格属于二维布局,我们几乎可以使用在弹性布局中同样的方式设置 Grid 项目在两个轴上的对齐效果。

我们已经解释了主轴,也就是 flex-direction 属性值定义的那个方向。交差轴(cross axis)是另一个维度。如果你设置了 flex-direction: row,主轴是沿着行的,而交差轴沿着列向下。如果设置的是 flex-direction: column,则主轴沿着列向下,而交差轴则沿着行。这就是我们要介绍的弹性布局的另一个重要特性,两个轴的方向与屏幕的物理 维度没有关系,我们没有讨论从左到右的行,或从上到下的列,是因为情况并非总是如此。

书写模式

上面在讲行和列的时候,提到了内联和块维度。本文使用英文书写的,是水平书写格式(译注:原文是英文,不过现在中文书写格式也与英文一样)。也就是当你为 Flexbox 指定为 row 排列方式的话,会得到水平排列的 Flex 项目。这种情况下,main-start 在左边——也就是英文句子开始的地方。

像在阿拉伯这样的语言方向从右向左的国家,那么起始边缘始于右边。

See the Pen Smashing Smashing Flexbox Series 1: row with rtl text by Rachel Andrew

当我只是创建了一个 Flex 容器的时候,Flexbox 的初始值表明 Flex 项目从右边开始,向左排列显示。内联方向(inline direction)的起始边缘就是我们使用的书写模式下句子开始的地方。

垂直书写模式(vertical writing mode)下的行是垂直的,因为在垂直语言环境下行就是垂直的,文本也是垂直显示的。我们在 Flex 容器上设置 writing-mode: vertical-lr 就能看到效果。这时候,当你设置 flex-directionrow 的时候,就能看到在垂直方向上显示的 Flex 项目。

See the Pen Smashing Smashing Flexbox Series 1: row with a vertical writing mode by Rachel Andrew

因此,行可以是水平显示,main-start 在左边或右边,当然也可以是垂直的,main-start 在顶部。当你习惯于水平排列思维,看到垂直书写模式下设置了 flex-direction: row 的 Flex 容器中的项目 是垂直排列的,可能感觉很奇怪,不过人家确实是在行的方向上排列的。

要让 Flex 项目在块维度上布局的话,为 flex-direction 设置 columncolumn-reverse。在英语环境下,我们看见 Flex 项目从 Flex 容器顶部开始、依次往下布局排列的。

在垂直书写模式下,块维度是横跨页面的,类似于此种模式下块元素的排列方式。如果设置了 vertical-lr,则块元素是从左到右排列的。

See the Pen Smashing Smashing Flexbox Series 1: column in vertical-lr writing mode by Rachel Andrew

但不论块元素是在什么方向显示排列的,如果 flex-direction 设置 column 的话,那么就是在块维度上布局的。

理解了行和列在不同的书写模式中,表现在不同的物理方向上,对理解 Grid 和 Flexbox 中的一些术语非常有用。我们没有在 Grid 和 Flexbox 中提到“从左到右”或是“从上到下”是因为我们没有做任何文档书写模式的假设。我们现在的 CSS 正在变得更加兼容书写模式,如果你想了解更多表现行为于此类似的其他属性和值的话,可以阅读我写的文章Logical Properties and Values

好了,我们来总结一下:

  • flex-direction: row
    • 主轴 = 内联维度
    • main-start 位于在给定书写模式下句子起始的地方
    • 交叉轴 = 块维度
  • flex-direction: column
    • 主轴 = 块维度
    • main-start 位于给定书写模式下块元起始布局的地方
    • 交叉轴 = 内联维度

默认对齐

使用 display: flex 后,还会有其他一些事情发生。在这个系列的之后的文章里,我会好好讲解一下对齐。本文中,我们先来看下使用 display: flex 后,使用的一些默认值。

注意: 对齐属性最开始起源于 Flexbox 规范。但正如 Flexbox 规范中解释的那样,Box Alignment 规范最终将取代 Flexbox 规范中定义的这些属性。

主轴对齐

justify-content 的初始值是 flex-start,就像我们在 CSS 中这样声明了:

.container {
    display: flex;
    justify-content: flex-start;
}
复制代码

这就是为什么 Flex 项目是从容器的起始边缘开始排列的原因。而 row-reverse 则是调换了起始边缘与结束边缘,结束边缘变为主轴开始的地方。

当你看见以 justify- 前缀开头的属性时,说明它是控制 Flexbox 主轴上对齐的。justify-content 操作主轴对齐,所有 Flex 项目在开始处对齐。

除了 flex-starjustify-content 还可使用的值包括:

  • flex-end
  • center
  • space-around
  • space-between
  • space-evently(在 Box Alignment 中添加)

这些值用于分配 Flex 容器可用空间(available space)。这是项目移动和间隔的原因。如果使用了 justify-content: space-between,可用空间在项目之间平均分配,当然了,前提是有空间可供分配。如果 Flex 容器空间过窄(所有项目 布局完成后没有额外的空间了),justify-content 就不起任何作用了。

我们可以设置 flex-directioncolumn 看下,此时 Flex 容器因为没有设置 height ,所以不存在剩余空间,justify-content: space-between 也不会起作用。如果你设置一个足够高的 height,待所有项目 布局好后,因为还有剩余空间,就会看到效果。

See the Pen Smashing Flexbox Series 1: column with a height by Rachel Andrew

交叉轴对齐

Flex 项目还可以在只有一行的 Flex 容器上,执行交叉轴对齐。这种对齐控制的是盒子们在这跟线上的对齐方式。下例中,有一个盒子的内容比其他的要多,然后其他盒子像被通知了一样伸展(stretch)到同样的高度。这是 align-items 属性初始值 stretch 在起作用:

See the Pen Smashing Guide to Layout: clearfix by Rachel Andrew

当你看见以 align- 前缀开头的属性时,说明它是控制 Flexbox 交叉轴上对齐的。align-items 操作交叉轴对齐,所有 Flex 项目在弹性线(flex line)内对齐。

除了 stretchalign-items 还可以取的值包括:

  • flex-start
  • flex-end
  • center
  • baseline

如果不想要盒子伸展到最高那个的高度,可以设置 align-items: flex-start。让项目在交叉轴的起始边缘对齐。

See the Pen Smashing Flexbox Series 1: align-items: flex-start by Rachel Andrew

Flex 项目的初始值

Flex 项目是有初始设置的,如下:

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

就是说项目默认不会扩展(grow)来填充主轴上的可用空间。如果给 flex-grow 设置了一个正值,那么项目就会扩展剩余的空间。

项目的 flex-shrink 属性处置值为正值 1,表示能收缩(shrink)。也就是说,当 Flex 容器比较窄的时候,在没有任何溢出发生的情况下,项目会变得尽可能小。这是一个明智的行为,一般来说,我们希望盒子里的内容不要溢出。

为了默认能够得到很好地布局,flex-basis 初始值使用了 auto。我会在以后的系列文章中解释这个属性的行为。你可以暂时把它认为是“大小刚刚好”(big enough to fit the content)。从页面表现上看的话,就是内容较多的那个项目分配到的空间会比内容少的要多。

See the Pen Smashing Flexbox Series 1: initial values of flex items by Rachel Andrew

这就是使用 Flexbox 带来的灵活性、设置了 flex-basisauto,在没有给项目设置额外大小限制的情况下爱,Flex 项目 有一个基础尺寸(base size)max-content。这个宽度是指项目中的内容在完全没有折行的情况下的长度。然后每个项目会 在占据的基础尺寸的基础上,按比例拿走一部分空间,这在 flexbox 规范中有详情描述。

“注意: Flex shrink 因子分配负空间的时候,是在 Flex 项目的基础尺寸的基础之上增加的。最终分配的负空间多少,是根据因子值,按比例分配到每个项目头上的。在大一点的项目 没有显著收缩之前,小的项目是不会收缩到零的。”

大一点的项目被抽取的空间相对(自身)来说是少的。你可以比较下面两张图,第一张图里每个项目 的内容量差不多,所以看起来差不多是一样宽的。在第二张图里,第三项目的内容比较多,结果看到它分配到了更多的空间。

内容多的项目分配到了更多的空间

Flexbox 在我们没有使用更多属性的前提下,尽可能的提供给我们一个合理的布局结果。与一刀切平均分配每个项目 一样的宽度、导致可能出现有内容多项目被挤压的非常高的情况不同的是,弹性布局会给内容多的项目 分配更多的显示空间。这种行为是弹性布局最佳的使用场景。 弹性布局最擅长于以一种灵活和内容感知的方式——沿着一个轴——放置一组项目。本文到此只是稍微接触了一些皮毛,在本系列后面的文章中我会适当地讲解这些算法。

总结

本文中,我们讲解了弹性布局中使用到的一些初始值,解释了当我们声明了 display: flex 的时候,实际发生了什么。一路分析下来,发现东西还是很多的,文中涉及到几个属性牵涉到弹性布局的许多关键特性。

弹性布局如此灵活:默认情况下,它试图对内容做出正确选择——压缩或拉伸 Flex 项目以获得最佳的可读性。弹性布局还支持书写模式(writing mode):行和列的方向与所使用的书写模式相关。通过选择空间的分布方式,弹性布局允许将 Flex 项目作为一个组在主轴上对齐;我们还可以在一个伸缩线(flex line)中对齐项目,在交叉轴上以彼此联系的方式移动 Flex 项目。重要的是,弹性布局能感知 Flex 项目的内容大小,会尝试在没有设置其他额外属性的情况下,来合理的分配空间给各个 项目。在以后的文章中,我们将更深入地讨论这些知识点,并做进一步分析什么时间以及为什么去选择使用弹性布局。

(完)