按我想法的话,css 网格布局(grid)和弹性布局(Flexbox)应该同时出现才对,这样网页布局方案就变得完整了。事实是,弹性布局先出现,因为使用弹性布局创建类网格(grid-type)系统比使用浮动更加便捷,于是我们便得到了许多基于 Flexbox 的网格系统。实际上,Flexbox 的优势并不是用来创建网格系统,这也是为什么有时我们在用它创建网格系统时,感觉很费劲的原因。
我准备开一个小系列的文章,花点时间解密一下 Flexbox——就像过去我在讲解 Grid 一样。我们将看到 Flexbox 的设计目的,它真正做得好的地方,以及为什么我们不选择它作为布局方法。本文中将会介绍当我们在使用 display: flex 的时候,到底发生了什么?
为了使用弹性布局,我们需要先有一个元素充当 Flex 容器的角色。容器使用 display: flex 声明:
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 容器表现的像个行内元素,其直接子元素将参与弹性布局。
理解了元素的外部显示模型和内部显示模型,对理解元素及其子元素在页面中的表现行为非常有帮助。你可以将这种思路带入到任意其他类型的盒子中:这个元素的表现特征为何?它的子元素呢?答案是跟外部显示模型和内部显示模型有关。
定义好 Flex 容器后,一些初始值就开始起作用了。在我们没有设置任何其他属性的情况下,所有的 Flex 项目 会排列为一行。之所以如此,是因为 flex-direction 的初始值是 row。
flex-direction 属性设置的是主轴(main axis)的方向,取值除了 row 之外,还包括:
这些排列在一行的 Flex 项目,始于内联维度(inline dimension)的起始边缘(start edge)、按照在源码中出现的结构顺序,依次排列。在规范中,这个“起始边缘”被称为 main-start:
main-start 位于内联维度的起始端
如果使用的是 column,则 Flex 项目从块维度(block dimension)起始边缘开始排列,从而形成一列。
main-start 位于块维度的起始端
如果使用的是 row-reverse,则 main-start 和 main-end 的位置就调换了。因此,Flex 项目会一个个按照与之前相反的顺序排列。
main-start 位于内联维度的终端
column-reverse 的作用于此类似。需要知道的是,这些值没有“改变 Flex 项目的顺序”,顺序变了只是表征而已,改变的其实是 Flex 项目从哪开始布局,也就是说改变的是 main-start 的位置。因此,Flex 项目按照反序显示,因为它们是沿着容器 的另一边开始布局的。
还有一个比较重要的点,就是 Flex 项目的排列顺序上的不同只是纯视觉上的。我们只是要求 Flex 项目从结束边缘(end edge)开始显示,对于屏幕阅读器(screen reader)或是当你按 Tab 键切换元素的时候,结果顺序还是在源码中出现的顺序。
上面多此重复按下 Tab 键,可以观察按钮被 focus 的顺序与在源码中出现的顺序一样,与显示顺序无关。
我们已经讲了弹性布局里一个非常重要的特性:主轴方向能从行切换到列。理解这种轴切换,能使我们更好地理解网格布局中对齐的工作原理。网格属于二维布局,我们几乎可以使用在弹性布局中同样的方式设置 Grid 项目在两个轴上的对齐效果。
我们已经解释了主轴,也就是 flex-direction 属性值定义的那个方向。交差轴(cross axis)是另一个维度。如果你设置了 flex-direction: row,主轴是沿着行的,而交差轴沿着列向下。如果设置的是 flex-direction: column,则主轴沿着列向下,而交差轴则沿着行。这就是我们要介绍的弹性布局的另一个重要特性,两个轴的方向与屏幕的物理 维度没有关系,我们没有讨论从左到右的行,或从上到下的列,是因为情况并非总是如此。
上面在讲行和列的时候,提到了内联和块维度。本文使用英文书写的,是水平书写格式(译注:原文是英文,不过现在中文书写格式也与英文一样)。也就是当你为 Flexbox 指定为 row 排列方式的话,会得到水平排列的 Flex 项目。这种情况下,main-start 在左边——也就是英文句子开始的地方。
像在阿拉伯这样的语言方向从右向左的国家,那么起始边缘始于右边。
当我只是创建了一个 Flex 容器的时候,Flexbox 的初始值表明 Flex 项目从右边开始,向左排列显示。内联方向(inline direction)的起始边缘就是我们使用的书写模式下句子开始的地方。
垂直书写模式(vertical writing mode)下的行是垂直的,因为在垂直语言环境下行就是垂直的,文本也是垂直显示的。我们在 Flex 容器上设置 writing-mode: vertical-lr 就能看到效果。这时候,当你设置 flex-direction 为 row 的时候,就能看到在垂直方向上显示的 Flex项目。
因此,行可以是水平显示,main-start 在左边或右边,当然也可以是垂直的,main-start 在顶部。当你习惯于水平排列思维,看到垂直书写模式下设置了 flex-direction: row 的 Flex容器中的项目 是垂直排列的,可能感觉很奇怪,不过人家确实是在行的方向上排列的。
要让 Flex 项目在块维度上布局的话,为 flex-direction 设置 column 或 column-reverse。在英语环境下,我们看见 Flex 项目从 Flex 容器顶部开始、依次往下布局排列的。
在垂直书写模式下,块维度是横跨页面的,类似于此种模式下块元素的排列方式。如果设置了 vertical-lr,则块元素是从左到右排列的。
但不论块元素是在什么方向显示排列的,如果 flex-direction 设置 column 的话,那么就是在块维度上布局的。
理解了行和列在不同的书写模式中,表现在不同的物理方向上,对理解 Grid 和 Flexbox 中的一些术语非常有用。我们没有在 Grid 和 Flexbox 中提到“从左到右”或是“从上到下”是因为我们没有做任何文档书写模式的假设。我们现在的 CSS 正在变得更加兼容书写模式,如果你想了解更多表现行为于此类似的其他属性和值的话,可以阅读我写的文章 Logical Properties and Values(https://www.smashingmagazine.com/2018/03/understanding-logical-properties-values/)。
好了,我们来总结一下:
使用 display: flex 后,还会有其他一些事情发生。在这个系列的之后的文章里,我会好好讲解一下对齐。本文中,我们先来看下使用 display: flex 后,使用的一些默认值。
注意: 对齐属性最开始起源于 Flexbox 规范。但正如 Flexbox 规范中解释的那样,BoxAlignment 规范最终将取代 Flexbox 规范中定义的这些属性。
justify-content 的初始值是 flex-start,就像我们在 CSS 中这样声明了:
.container { display: flex; justify-content: flex-start; }
这就是为什么 Flex 项目是从容器的起始边缘开始排列的原因。而 row-reverse 则是调换了起始边缘与结束边缘,结束边缘变为主轴开始的地方。
当你看见以 justify- 前缀开头的属性时,说明它是控制 Flexbox 主轴上对齐的。justify-content 操作主轴对齐,所有 Flex 项目在开始处对齐。
除了 flex-star,justify-content 还可使用的值包括:
这些值用于分配 Flex 容器可用空间(available space)。这是项目移动和间隔的原因。如果使用了 justify-content: space-between,可用空间在项目之间平均分配,当然了,前提是有空间可供分配。如果 Flex 容器空间过窄(所有项目 布局完成后没有额外的空间了),justify-content 就不起任何作用了。
我们可以设置 flex-direction 为 column 看下,此时 Flex 容器因为没有设置 height ,所以不存在剩余空间,justify-content: space-between 也不会起作用。如果你设置一个足够高的 height,待所有项目 布局好后,因为还有剩余空间,就会看到效果。
Flex 项目还可以在只有一行的 Flex 容器上,执行交叉轴对齐。这种对齐控制的是盒子们在这跟线上的对齐方式。下例中,有一个盒子的内容比其他的要多,然后其他盒子像被通知了一样伸展(stretch)到同样的高度。这是 align-items 属性初始值 stretch 在起作用:
当你看见以 align- 前缀开头的属性时,说明它是控制 Flexbox 交叉轴上对齐的。align-items 操作交叉轴对齐,所有 Flex 项目在弹性线(flex line)内对齐。
除了 stretch,align-items 还可以取的值包括:
如果不想要盒子伸展到最高那个的高度,可以设置 align-items: flex-start。让项目在交叉轴的起始边缘对齐。
Flex 项目是有初始设置的,如下:
就是说项目默认不会扩展(grow)来填充主轴上的可用空间。如果给 flex-grow 设置了一个正值,那么项目就会扩展剩余的空间。
项目的 flex-shrink 属性处置值为正值 1,表示能收缩(shrink)。也就是说,当 Flex 容器比较窄的时候,在没有任何溢出发生的情况下,项目会变得尽可能小。这是一个明智的行为,一般来说,我们希望盒子里的内容不要溢出。
为了默认能够得到很好地布局,flex-basis 初始值使用了 auto。我会在以后的系列文章中解释这个属性的行为。你可以暂时把它认为是“大小刚刚好”(big enough to fit the content)。从页面表现上看的话,就是内容较多的那个项目分配到的空间会比内容少的要多。
这就是使用 Flexbox 带来的灵活性、设置了 flex-basis 为 auto,在没有给项目设置额外大小限制的情况下爱,Flex 项目 有一个基础尺寸(base size)max-content。这个宽度是指项目中的内容在完全没有折行的情况下的长度。然后每个项目会 在占据的基础尺寸的基础上,按比例拿走一部分空间,这在 flexbox 规范中有详情描述。
“注意: Flex shrink 因子分配负空间的时候,是在 Flex 项目的基础尺寸的基础之上增加的。最终分配的负空间多少,是根据因子值,按比例分配到每个项目头上的。在大一点的项目 没有显著收缩之前,小的项目是不会收缩到零的。”
大一点的项目被抽取的空间相对(自身)来说是少的。你可以比较下面两张图,第一张图里每个项目 的内容量差不多,所以看起来差不多是一样宽的。在第二张图里,第三项目的内容比较多,结果看到它分配到了更多的空间。
内容多的项目分配到了更多的空间
Flexbox 在我们没有使用更多属性的前提下,尽可能的提供给我们一个合理的布局结果。与一刀切平均分配每个项目 一样的宽度、导致可能出现有内容多项目被挤压的非常高的情况不同的是,弹性布局会给内容多的项目 分配更多的显示空间。这种行为是弹性布局最佳的使用场景。 弹性布局最擅长于以一种灵活和内容感知的方式——沿着一个轴——放置一组项目。本文到此只是稍微接触了一些皮毛,在本系列后面的文章中我会适当地讲解这些算法。
本文中,我们讲解了弹性布局中使用到的一些初始值,解释了当我们声明了 display: flex 的时候,实际发生了什么。一路分析下来,发现东西还是很多的,文中涉及到几个属性牵涉到弹性布局的许多关键特性。
弹性布局如此灵活:默认情况下,它试图对内容做出正确选择——压缩或拉伸 Flex 项目以获得最佳的可读性。弹性布局还支持书写模式(writing mode):行和列的方向与所使用的书写模式相关。通过选择空间的分布方式,弹性布局允许将 Flex 项目作为一个组在主轴上对齐;我们还可以在一个伸缩线(flex line)中对齐项目,在交叉轴上以彼此联系的方式移动 Flex 项目。重要的是,弹性布局能感知 Flex 项目的内容大小,会尝试在没有设置其他额外属性的情况下,来合理的分配空间给各个 项目。在以后的文章中,我们将更深入地讨论这些知识点,并做进一步分析什么时间以及为什么去选择使用弹性布局。
作者:zhangbao90s
链接:https://juejin.im/post/5d2da274f265da1bb003f242
著作权归作者所有。