阅读 3228

[译] 当你创建 Flexbox 布局时,都发生了什么?

快速简介: 在我的理想世界里,CSS Grid 和 Flexbox 应当作为一个整体一起出现,才能组成完整的网页布局系统。然而,我们先有了 Flexbox 属性,因为它比浮动(floats)更适合用来做网格布局,所以出现了很多以 Flexbox 为基础的网格布局系统。事实上,很多人觉得 Flexbox 弹性布局令人困扰或者难以理解,都是因为尝试把它作为网格布局的方法。

在接下来的一系列文章里面,我会花一点时间来详细讲解 Flexbox 弹性布局 —— 以之前我用来理解 grid 属性的方式。我们一起看看 Flexbox 设计是为了什么,它擅长处理什么,以及我们为什么不选择它作为布局的方法。在这篇文章中,我们会详细看一下,当你在样式表添加 display: flex 的时候,究竟会发生什么。

一个 Flex 容器,拜托了!

为了使用 Flexbox 布局,你需要一个元素作为 flex 容器,在 CSS 中,使用 display: flex

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: row-reverse;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: display: flex;

让我们花一点点时间来思考一下到底 display: flex 意味着什么。在 Display Module Level 3 中,display 的每一个值都由两个部分构成:内部显示模式和外部显示模式。当我们使用 display: flex 时,我们其实定义的是 display: block flex。flex 容器的外部显示模式是 block,它在文档流中显示为正常的块级元素。内部显示模式是 flex,所以在容器内部的直接子元素按照弹性布局来排列。

可能你之前没仔细想过,但其实已经知道了。flex 容器在页面中和其他块级元素的表现一样。如果你在 flex 容器后面紧跟一个段落,它们两个都会表现为正常的块级元素。

我们也可以把容器的属性设置为 inline-flex,就和设置成 display:inline flex 一样。比如说有一个行内级别的 flex 容器,容器里还有一些参与 flex 布局的子元素。这个 flex 容器内的子元素的表现就和块级 flex 容器内的子元素表现一样,不同之处就在于容器本身在整体布局中的表现。

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>

<em>这个 flex 容器是行内元素,所以另一个行内元素会紧跟它后面显示。</em>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: inline-flex;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: display: inline-flex;

元素的外部显示模式决定了它作为盒模型在页面中怎样显示,同时与内部显示模式一起,决定了其子元素的行为。这是一个很有用的思想。你可以把这种思想应用于任何 CSS 定义的盒模型。这个元素将会如何表现?它的子元素又会怎样表现?这些问题的答案就与它们的内部显示模式和外部显示模式有关。

行或者列?

一旦我们定义了 flex 容器之后,一些默认值就开始发挥作用了。在我们没有添加任何其他属性的情况下,flex 子项目(flex item)会按照行来排列。这是因为 flex-direction 属性的默认值就是 row。如果你不对它进行设置,它就会按照行的方向来显示。flex-direction 属性是用来设置主轴(main axis)的排列方向,这个属性还有其他的值:

  • column
  • row-reverse
  • column-reverse

当子项目排成一行的时候,子项目会按照在文档中的顺序,从行内维度的起始边缘依次排列。在规范中,这个边缘就被叫做 main-start

main-start 是一行的开始

main-start 是行内维度的起始位置(Large preview)。

如果我们使用 column,子项目从块级维度的起始边缘开始排列,因此构成一列。

子项目按照列来排列,main-start 位于顶部

main-start 是块级维度的起始位置(Large preview)。

当我们使用 row-reverse 时,main-startmain-end 的位置互换了,因此,子项目也会相应的按照相反的顺序来排列。

子项目从行的末尾开始排列

main-start 在内联维度的末尾(Large preview)。

column-reverse 也具有一样的效果。另外还有一点很重要,那就是这些值并不会“改变子项目的顺序”,尽管它们看起来是这样的效果,它们改变的是这些子项目开始排列的位置:通过改变 main-start 来达到目的。所以我们的子项目会按照相反的方向来排列,这仅仅是因为它是从容器的结束位置开始排列的。

另外还有一件很重要的事情要记住,当排列顺序发生改变时,这仅仅是视觉上的,因为我们要求子项目从结束位置开始排列。它们在文档中仍然是原来的顺序,当你使用屏幕阅读器的时候仍然是按照源文档的顺序进行索引。如果你真的想要改变子元素的顺序,不应该使用 row-reverse,而是直接在文档源中去改变子项目的顺序。

Flexbox 的两条轴线

我们已经讲解了 flexbox 的一个重要特性:能够将主轴(main axis)的方向从行切换为列。这种轴的方向切换,就是为什么我常常认为网格布局中的对齐更容易理解的原因。因为在网格布局中,在两个方向上你都可以采用几乎相同的方式来实现对齐。而对于弹性布局来说会更麻烦点,因为在主轴(main axis)和交叉轴(cross axis)上,子项目的表现是不太相同的。

我们已经了解了主轴(main axis),即你用 flex-direction 属性的值来定义的那根轴线。交叉轴(cross axis)则在另一个方向上。如果你设置 flex-direction: row,那么你的主轴(main axis)是沿着行的方向,你的交叉轴(cross axis)是沿着列的方向。如果设置 flex-direction: column,主轴(main axis)是沿着列的方向,交叉轴(cross axis)是沿着行的方向。在这里我们就需要讨论 flexbox 的另外一个重要特点,那就是它与屏幕的物理方向无关。我们不讨论从左到右方向的行,或从上到下方向的列,因为情况并非总是如此。

书写模式

当我在上文中描述行和列的时候,我提到了块级和行内的维度 dimensions。这篇文章是用英文写的,它是水平的书写模式。这就意味着当你要 Flexbox 显示一行时,子项目会水平的展示。在这个例子中,main-start 位于左边 —— 也就是英文书写模式中中句子开始的位置。

如果我使用的是从右到左书写的语言,比如阿拉伯语的话,起始位置就会位于右边:

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: row;
  direction: rtl;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: row with rtl text

flexbox 的初始值意味着,如果我所做的只是创建一个 flex 容器,我的子项目将会从右侧开始显示,并且向左排列。内联方向的起始位置是你正在使用的书写模式中句子开始的位置

如果你使用垂直书写模式并且使用的默认排列方向(这里指 flex-direction: row),此时的行就会是垂直方向的,因为这就是垂直书写方式语言排列行的方式。你可以尝试为 flex 容器设置 writing-mode 属性,把值设置为 vertical-lr。现在,你再把 flex-direction 设置为 row,子项目就会排成垂直的一列了。

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: row;
  writing-mode: vertical-lr;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: row with a vertical writing mode

所以,一行可以水平的排列,main-start 位于左侧或者右侧,也可以垂直排列,main-start 位于顶部。即使我们的思维习惯了横向排列的文本,很难想象一行垂直排列的文本,但它的 flex-direction 属性的值仍然是 row

为了让子项目按照块级维度进行排列,我们可以把 flex-direction 的值设置成 column 或者 column-reverse。在英语(或者阿拉伯语)这样的水平书写模式里,子项目会从容器顶部开始按照垂直方向排列。

在垂直书写模式中,块级方向横跨整个页面,这也是这种书写模式下块级元素的排列方向。如果你将一列设置为为 vertical-lr,那么这些块级元素会从左到右进行排列(内部的文本方向仍为垂直排列)。

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: column;
  writing-mode: vertical-lr;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: column in vertical-lr writing mode

但是,无论块级元素怎么显示,只要你使用的是 column 方向,那么元素始终处在块级维度之中。

了解一行或者一列能够在不同的物理方向上运行,有助于我们理解网格布局和弹性布局中的一些术语。在网格布局和弹性布局中,并不会使用『左和右』、『上和下』这样的方向,因为我们并不会指定文档的书写模式。现在所有的 CSS 都变的更注重书写模式了。如果你对其他已经支持这种方向差异的 CSS 属性和值有兴趣的话,可以读一下我的这篇文章 Logical Properties and Values

总结一下,记住:

  • flex-direction: row

    • 主轴 = 行内维度
    • main-start 位于当前书写模式下句子开头的位置
    • 交叉轴 = 块级维度
  • flex-direction: column

    • 主轴 = 块级维度
    • main-start 位于当前书写模式下块级元素的开头位置
    • 交叉轴 = 行内维度

默认对齐方式

当我们设置 display: flex 时,还会发生一些事情,默认的对齐方式会发挥作用。在该系列的其他文章中,我们会好好地了解一下对齐方式。但是,我们现在在探索 display: flex 的时候,也应该看一下这些发挥作用的默认值。

注意值得注意的是,尽管这些对齐属性始于 Flexbox 规范,但 Box Alignment(盒模型对齐)会最终覆盖 Flexbox 规范的相关内容,如 flexbox 规范中所述。

主轴对齐方式

justify-content 属性的默认值是 flex-start,就像我们的 CSS 写的那样:

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

这就是我们的 flex 子项目(flex item)从 flex 容器的起始边缘开始排列的原因。同样也是当我们设置 row-reverse 时,它们变为从结束边缘开始排列的原因,因为那个边缘变成了主轴(main axis)的起点。

当你看见对齐属性以 justify- 开头时,这个属性就是作用于主轴(main axis)的。也就是说 justify-content 属性规定主轴(main axis)的对齐方式,并将子项目从起始边缘对齐。

justify-content 还有其他的值:

  • flex-end
  • center
  • space-around
  • space-between
  • space-evenly(在块级对齐方式中添加)

这些值用来处理 flex 容器中剩余空间的分布。这就是子项目间会发生移动或者说相互分隔的原因了。如果你添加属性 justify-content: space-between,剩余空间被平均分配给子项目。当然,这只有在容器有剩余空间的情况下才会发生。如果你的 flex 容器被全部充满了(子项目排列完后,没有任何剩余空间),那么设置 justify-content 属性不会产生任何效果。

你可以把 flex-direction 设置成 column。因为 flex 容器在没有高度的情况下不会有剩余空间,所以设置 justify-content: space-between 不会发生变化。如果你把容器设置成比展示所需要的高度更高的话,那么这个属性就会发挥作用了:

HTML:

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 500px;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: column with a height

交叉轴的对齐方式

子项目在单一交叉轴的 flex 容器中也会沿着这根交叉轴对齐。这里执行的对齐是子项目沿着交叉轴相互对齐。在接下来的这个例子中,有一个子项目比其他项占据更高的空间,然后其他的子项目会按照某种规范来拉伸到与它相同的高度,这个规范就是 align-items 属性,因为它的初始值就是 stretch

HTML:

<div class="container">
  <div class="item">One</div>
  <div class="item">Two</div>
  <div class="item">
    <ul>
    <li>Three: a</li>
    <li>Three: b</li>
    <li>Three: c</li>
    </ul>
    </div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
}

.item {
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}

.item ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Guide to Layout: clearfix

当你在 flex 布局中看到有一个属性是以 align- 开头的,那个就是交叉轴的对齐方式,align-items 属性规定子项目在沿着交叉轴方向上的对齐方式,这个属性的其他的值包括:

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

如果你不想其他的子项目跟拉伸到跟最高的那一项一样高的话,设置 align-items: flex-start,它会把子项目都沿着交叉轴的起始位置对齐。

HTML:

<div class="container">
  <div class="item">One</div>
  <div class="item">Two</div>
  <div class="item">
    <ul>
    <li>Three: a</li>
    <li>Three: b</li>
    <li>Three: c</li>
    </ul>
    </div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  align-items: flex-start;
}

.item {
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}

.item ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: align-items: flex-start

flex 子项目的初始值

终于说到这里了,flex 子项目(flex item)也是有初始值的,它们包括:

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

这意味我们的子项目(flex item)在默认情况下不会自动充满主轴上的剩余空间。如果 flex-grow 被设置成一个正数,才会导致子项目拉伸并占据剩余空间。

这些子项目(flex item)同样可以收缩。默认情况下,flex-shrink 的值被设置成了 1。这就意味着,如果我们的 flex 容器非常小,那么其中的子元素在溢出容器之前就会自动的缩小以适应容器大小。这是一个非常灵活的属性。总的来说就是,子项目在容器没有足够空间去排列的情况下依然能保持在容器之内,并且不会溢出。

为了在默认情况下获得最好的展示效果,flex-basis 属性的默认值被设置成 auto,我们会在这个系列的其他文章中好好了解这代表什么。现在,你只需要将 auto 理解为『大到足够适应容器』就行了。在这种情况下,当 flex 容器中有一些子项目,其中的一个子项目相较于其他包含更多的内容,那么它会被分配更多的空间。

HTML:

<div class="container">
  <div class="item">Two words</div>
  <div class="item">Now three words</div>
  <div class="item">
    This flex item has a lot of content and so it is going to need more space in the flex container.
    </div>
</div>
复制代码

CSS:

body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {  
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  width: 400px;
}

.item {
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}
复制代码

请看 Rachel Andrew(@rachelandrew)在 CodePen 上的这个例子:Smashing Flexbox Series 1: initial values of flex items

上面这个例子就是弹性布局灵活性的体现。在 flex-basis 属性的默认值是 auto,并且子项目(flex item)没有设置尺寸的情况下,它就会有一个 max-content 的基础尺寸。这就是它根据内容自动延伸之后没有经过任何其他包装的尺寸。然后,子项目按照比例来占据剩余空间,详见以下 flexbox 规范中的说明。

『注意:分配收缩空间时,是根据基本尺寸乘以弹性收缩系数(flex shrink factor)。收缩空间的大小与子项目设置的 flex-shrink 的大小成比例。比如说有一个较小的子项目,在其他较大的子项目明显收缩之前,是不会被收缩到没有空间的。』

也就是说较大的子项目收缩的空间更多,那么现在我们得到了最终的布局结果。你可以比较一下下面两个截图,都是使用上面的例子,不同的是在第一个截图中,第三个盒子内容较少占据的空间较小,因此相对的每一列的占据的空间更均匀一些。

这是较大的子项目占据更多空间的例子

其他项为了给较大的一项提供空间而自动收缩(Large preview)。

弹性布局会试图帮助我们获得一个合理的最终显示结果,而不需要写 CSS 的人来定义。它不会平均的减少每行的宽度,从而形成一个每行只有几个单词的很高的子项目,而是会给该子项目分配更多的空间用以展示其内容。这种表现正是如何正确弹性布局的关键。它最适用于用于沿着一条轴线排列的元素,以一种灵活和感知内容的方式。这里我简单介绍了一点细节,但在接下来的系列文章中我们会更加深入的了解这些算法。

总结

在这篇文章中,我用弹性布局的属性的一些默认值来介绍当你设置 display: flex 的时候,究竟发生了什么。令人惊讶的是,当你逐步分解之后,发现它原来有这么多内容,并且这些内容就包含了弹性布局的核心特点。

弹性布局是非常灵活的:它会根据你的内容自动地做出不错的选择 —— 通过收缩和拉伸达到最好的展示效果。弹性布局还能感知书写模式:布局中行和列的方向跟书写模式有关。弹性布局通过分配空间,允许子项目在主轴(main axis)上以整体的的方式来对齐。它还允许子项目按照交叉轴来对齐,使得交叉轴上的项目相互关联。更重要的是,弹性布局知道你的内容有多大,并且尽量采用更好的方式来展示你的内容。在接下来的文章中,我们会更加深入的探索,思考我们什么时候以及为什么要用弹性布局。

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

关注下面的标签,发现更多相似文章
评论