flex 布局的基本概念

布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。2009年,W3C 提出了一种新的方案----Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

当然还有一种布局解决方案是grid布局,但本文先就flex进行梳理。

Flexible Box 模型,通常被称为 flexbox,是一种一维的布局模型。它给 flexbox 的子元素之间提供了强大的空间分布和对齐能力。本文给出了 flexbox 的主要特性,更多的细节将在别的文档中探索。

我们说 flexbox 是一种一维的布局,是因为一个 flexbox 一次只能处理一个维度上的元素布局,一行或者一列。作为对比的是另外一个二维布局 CSS Grid Layout,可以同时处理行和列上的布局。

flexbox的两根轴线

当使用 flex 布局时,首先想到的是两根轴线 — 主轴和交叉轴。主轴由 flex-direction 定义,另一根轴垂直于它。我们使用 flexbox 的所有属性都跟这两根轴线有关,所以有必要在一开始首先理解它。

主轴

主轴由 flex-direction 定义,可以取 4 个值:

  • row
  • row-reverse
  • column
  • column-reverse

如果你选择了 row 或者 row-reverse,你的主轴将沿着 inline 方向延伸,也就是水平方向延伸。

选择 column 或者 column-reverse 时,你的主轴会沿着上下方向延伸 — 也就是 block 排列的方向,也就是竖直方向延伸。

交叉轴

交叉轴垂直于主轴,所以如果你的flex-direction (主轴) 设成了 row 或者 row-reverse 的话,交叉轴的方向就是沿着列向下的。

如果主轴方向设成了 column 或者 column-reverse,交叉轴就是水平方向。

起始线和终止线

另外一个需要理解的重点是 flexbox 不会对文档的书写模式提供假设。过去,CSS 的书写模式主要被认为是水平的,从左到右的。现代的布局方式涵盖了书写模式的范围,所以我们不再假设一行文字是从文档的左上角开始向右书写,新的行也不是必须出现在另一行的下面。

如果 flex-direction 是 row ,并且我是在书写英文,那么主轴的起始线是左边,终止线是右边。如果我在书写阿拉伯文,那么主轴的起始线是右边,终止线是左边。在这两种情况下,交叉轴的起始线是 flex 容器的顶部,终止线是底部,因为两种语言都是水平书写模式。

Flex容器

文档中采用了 flexbox 的区域就叫做 flex 容器。为了创建 flex 容器,我们把一个容器的 display 属性值改为 flex 或者 inline-flex。完成这一步之后,容器中的直系子元素就会变为 flex 元素。所有 CSS 属性都会有一个初始值,所以 flex 容器中的所有 flex 元素都会有下列行为:

  • 元素排列为一行 (flex-direction 属性的初始值是 row)。
  • 元素从主轴的起始线开始。
  • 元素不会在主维度方向拉伸,但是可以缩小。
  • 元素被拉伸来填充交叉轴大小。
  • flex-basis 属性为 auto。
  • flex-wrap 属性为 nowrap。

这会让你的元素呈线形排列,并且把自己的大小作为主轴上的大小。如果有太多元素超出容器,它们会溢出而不会换行。如果一些元素比其他元素高,那么元素会沿交叉轴被拉伸来填满它的大小。

用flex-wrap实现多行容器

虽然flexbox是一维模型,但可以使我们的flex项目应用到多行中。在这样做的时候,您应该把每一行看作一个新的flex容器。任何空间分布都将在该行上发生,而不影响该空间分布的其他行。

为了实现多行效果,请为属性flex-wrap添加一个属性值wrap。现在,如果您的项目太大而无法全部显示在一行中,则会换行显示。

对于flex容器,项目的子元素总宽度大于容器最大宽度。由于flex-wrap的值设置为wrap,所以项目的子元素换行显示。若将其设置为nowrap,这也是初始值,它们将会缩小以适应容器,因为它们使用的是允许缩小的初始Flexbox值。如果项目的子元素无法缩小,使用nowrap会导致溢出,或者缩小程度还不够小

简写属性 flex-flow

你可以将两个属性 flex-direction 和 flex-wrap 组合为简写属性 flex-flow。第一个指定的值为 flex-direction ,第二个指定的值为 flex-wrap.

flex元素上的属性

为了更好地控制 flex 元素,有三个属性可以作用于它们:

  • flex-grow
  • flex-shrink
  • flex-basis

在考虑这几个属性的作用之前,需要先了解一下 可用空间 available space 这个概念。这几个 flex 属性的作用其实就是改变了 flex 容器中的可用空间的行为。同时,可用空间对于 flex 元素的对齐行为也是很重要的。

假设在 1 个 500px 的容器中,我们有 3 个 100px 宽的元素,那么这 3 个元素需要占 300px 的宽,剩下 200px 的可用空间。在默认情况下,flexbox 的行为会把这 200px 的空间留在最后一个元素的后面。

如果期望这些元素能自动地扩展去填充满剩下的空间,那么我们需要去控制可用空间在这几个元素间如何分配,这就是元素上的那些 flex 属性要做的事。

flex-basis

flex-basis 定义了该元素的空间大小(the size of that item in terms of the space),flex 容器里除了元素所占的空间以外的富余空间就是可用空间 available space。该属性的默认值是 auto 。此时,浏览器会检测这个元素是否具有确定的尺寸。在上面的例子中,所有元素都设定了宽度(width)为 100px,所以 flex-basis 的值为 100px

如果没有给元素设定尺寸,flex-basis 的值采用元素内容的尺寸。这就解释了:我们给只要给 Flex 元素的父元素声明 display: flex ,所有子元素就会排成一行,且自动分配小大以充分展示元素的内容。

flex-grow

flex-grow 若被赋值为一个正整数,flex 元素会以 flex-basis 为基础,沿主轴方向增长尺寸。这会使该元素延展,并占据此方向轴上的可用空间(available space)。如果有其他元素也被允许延展,那么他们会各自占据可用空间的一部分。

如果我们给上例中的所有元素设定 flex-grow 值为 1,容器中的可用空间会被这些元素平分。它们会延展以填满容器主轴方向上的空间。

flex-grow 属性可以按比例分配空间。如果第一个元素 flex-grow 值为 2,其他元素值为 1,则第一个元素将占有 2/4(上例中,即为 200px 中的 100px), 另外两个元素各占有 1/4(各 50px)。

flex-shrink

flex-grow属性是处理 flex 元素在主轴上增加空间的问题,相反flex-shrink属性是处理 flex 元素收缩的问题。如果我们的容器中没有足够排列 flex 元素的空间,那么可以把 flex 元素flex-shrink属性设置为正整数来缩小它所占空间到flex-basis以下。与flex-grow属性一样,可以赋予不同的值来控制 flex 元素收缩的程度 —— 给flex-shrink属性赋予更大的数值可以比赋予小数值的同级元素收缩程度更大。

在计算 flex 元素收缩的大小时,它的最小尺寸也会被考虑进去,就是说实际上 flex-shrink 属性可能会和 flex-grow 属性表现的不一致。

flex属性简写

你可能很少看到 flex-grow,flex-shrink,和 flex-basis 属性单独使用,而是混合着写在 flex 简写形式中。 Flex 简写形式允许你把三个数值按这个顺序书写 — flex-grow,flex-shrink,flex-basis。

大多数情况下可以用预定义的简写形式。在这个教程中你可能经常会看到这种写法,许多情况下你都可以这么使用。下面是几种预定义的值:

  • flex: initial
  • flex: auto
  • flex: none
  • flex:

flex: initial 是把 flex 元素重置为 Flexbox 的初始值,它相当于 flex: 0 1 auto。在这里 flex-grow 的值为 0,所以 flex 元素不会超过它们 flex-basis 的尺寸。flex-shrink 的值为 1, 所以可以缩小 flex 元素来防止它们溢出。flex-basis 的值为 auto. Flex 元素尺寸可以是在主维度上设置的,也可以是根据内容自动得到的。

flex: auto 等同于 flex: 1 1 auto;和上面的 flex:initial 基本相同,但是这种情况下,flex 元素在需要的时候既可以拉伸也可以收缩。

flex: none 可以把 flex 元素设置为不可伸缩。它和设置为 flex: 0 0 auto 是一样的。元素既不能拉伸或者收缩,但是元素会按具有 flex-basis: auto 属性的 flexbox 进行布局。

你在教程中常看到的 flex: 1 或者 flex: 2 等等。它相当于flex: 1 1 0。元素可以在flex-basis为 0 的基础上伸缩。

元素的对齐和空间分配

Flexbox 的一个关键特性是能够设置 flex 元素沿主轴方向和交叉轴方向的对齐方式,以及它们之间的空间分配。

align-items

align-items 属性可以使元素在交叉轴方向对齐。

这个属性的初始值为stretch,这就是为什么 flex 元素会默认被拉伸到最高元素的高度。实际上,它们被拉伸来填满 flex 容器 —— 最高的元素定义了容器的高度。

你也可以设置align-items的值为flex-start,使 flex 元素按 flex 容器的顶部对齐,flex-end 使它们按 flex 容器的下部对齐,或者center使它们居中对齐。在实例中尝试——我给出了 flex 容器的高度,以便你可以看到元素在容器中移动。

  • stretch
  • flex-start
  • flex-end
  • center

justify-content

justify-content属性用来使元素在主轴方向上对齐,主轴方向是通过 flex-direction 设置的方向。初始值是flex-start,元素从容器的起始线排列。但是你也可以把值设置为flex-end,从终止线开始排列,或者center,在中间排列。

你也可以把值设置为space-between,把元素排列好之后的剩余空间拿出来,平均分配到元素之间,所以元素之间间隔相等。或者使用space-around,使每个元素的左右空间相等。

下列justify-content属性的值:

  • stretch
  • flex-start
  • flex-end
  • center
  • space-around
  • space-between

子元素在主轴上的比例

在 html 中若一个元素的 css 设置为 display: flex,则这个元素就是 flex container , 其子元素为 flex item

我们将探索应用于 flex 子元素的三个属性,它们能使我们在主轴方向上控制 flex 子元素的尺寸和伸缩性— flex-grow, flex-shrink, 和 flex-basis. 充分了解这些属性如何与增长和缩小的 flex 子元素一起工作是掌握 Flex 布局的关键所在。

这三个属性控制一个 flex 子元素的以下几个方面:

  • flex-grow: 这个 flex 子元素得到(伸张)多少 positive free space?
  • flex-shrink: 从这个 flex 子元素要消除(收缩)多少 negative free space?
  • flex-basis: 在 flex 子元素未伸张和收缩之前,它的大小是多少?

工作于主轴的重要概念

在考虑 flex 属性如何在主轴方向上控制比率之前,有一些概念值得我们去深究。这涉及到 flex 子元素在任何伸缩之前的自然尺寸,以及自由空间的概念

Flex子元素的尺寸

为了计算出有多少可用空间能布局于 flex 子元素,浏览器必须知道这个 item 有多大才能开始。它是如何解决没有应用于绝对单位的宽度和高度的 flex 子元素?

在 min-content 和 max-content 的 CSS 中有一个概念— 这些关键字定义在 CSS Intrinsic and Extrinsic Sizing Specification, 并且可以用一个 length单位代替。

例如,我有两段包含一个文本字符串的段落。第一段设置了min-content的宽度。在支持这个关键字的浏览器你可以看见文本已尽可能抓住机会来自动换行,变得尽可能小且没有溢出。此之后就是那个字符串的 min-content 大小。本质上讲,字符串中最长的单词决定了大小。

第二段设置了 max-content值,且它和 min-content 相反。它会变得尽可能大,没有自动换行的机会。如果 flex 容器太窄,它就会溢出其自身的盒子。

正负自由空间

positive and negative free space 直译过来就是正负空闲空间,为了读者能良好的阅读下文,译者先大概说下。

Flex 布局中有 flex 容器和 flex 子元素,flex 子元素包含在 flex 容器中,那么当 flex 子元素在主轴上的尺寸(大小)之和小于 flex 容器 的尺寸时,flex 容器中就会有多余的空间没有被填充,这些空间就被叫做 positive free space。当 flex 子元素在主轴上的尺寸之和大于 flex 容器的尺寸时,flex 容器的空间就不够用,此时 flex 子元素的尺寸之和减去 flex 容器的尺寸(flex 子元素溢出的尺寸)就是negative free space, 这个 negative free space 加上 flex 容器的尺寸刚好可以容纳 flex 子元素。

去谈论这些属性之前我们需要理解 positive free space 和 negative free space 的概念。当一个 flex 容器有 positive free space 时,它就有更多的空间用于在 flex 容器内显示 flex 子元素。比如说,如果我有 500px 宽的 flex 容器,flex-direction 属性值为 row, 三个 100px 宽的 flex 子元素,而且我还有 200px 的 positive free space, 那么如果我想让它们(positive free space)填充 flex 容器,它们就可以填充在 flex 子元素之间。

当 flex 子元素的自然尺寸加起来比 flex 容器内的可用空间大时,我们产生了 negative free space. 如果我有一个像上面 500px 宽的 flex 容器,但是三个 flex 子元素每个都为 200px 宽,我就一共需要 600px 宽,因此我就有了 100px 的 negative free space. 这可以从 flex 子元素中删除以使其能适应 flex 容器。

flex-basis 属性

flex-basis 属性在任何空间分配发生之前初始化 flex 子元素的尺寸。此属性的初始值为 auto. 如果 flex-basis 设置为 auto , 浏览器会先检查 flex 子元素的主尺寸是否设置了绝对值再计算出 flex 子元素的初始值。比如说你已经给你的 flex 子元素设置了 200px 的宽,则 200px 就是这个 flex 子元素的 flex-basis.

如果你的 flex 子元素 为自动调整大小,则auto 会解析为其内容的大小。此时你所熟知的 min-content 和 max-content 大小会变得有用,flexbox 会将 flex 子元素的 max-content 大小作为 flex-basis. 下面的例子可以证明这一点。

除了关键字 auto 以外,你还可以使用关键字 content 作为 flex-basis的值。这会导致 flex-basis 根据内容大小设置即使 flex 子元素 设置了宽度。这是一个新关键字而且获得浏览器支持的比较少,但是你还是可以通过设置flex-basis: auto并确保你的 flex 子元素没有设置宽度来达到相同的效果 , 以便它能自动调整大小。

空间分配时,如果你想 flexbox 完全忽略 flex 子元素的尺寸就设置flex-basis 为 0. 这基本上告诉 flexbox 所有空间都可以抢占,并按比例分享。

flex-grow 属性

flex-grow 属性指定了flex 增长值, 这决定了当 positive free space 分配时,flex 子元素相对于 flex 容器中的其余 flex 子元素的增长程度。

如果你所有的 flex 子元素 设置了相同的flex-grow属性值,那么空间将会在 flex 子元素 之间平均分配。如果你想要这种情形,通常你需要使用1作为值,而且如果你喜欢你还可以将它们的flex-grow都 设置为88,或100,或1.2 —这只是个比例。如果 flex-grow 的值全部相同,并且在 flex 容器 中还有 positive free space , 那么它(positive free space)就会平均的分配给所有的 flex 子元素

结合flex-grow 与 flex-basis

根据flex-grow和flex-basis的相互影响,有些东西会变得迷惑起来。让我们考虑三个不同内容大小的 flex 子元素的例子,应用于下列 flex 规则:

flex: 1 1 auto;

这个例子中设置flex-basis的值为auto且没有设置它们的宽,因此它们是自动调整大小的。这意味着 flexbox 的大小决定于全部 flex 子元素内容的max-content 大小。在布局完 flex 子元素 之后在 flex 容器中还有一些 positive free space,展示在这幅图片的阴影区域中:

我们使用与内容大小相等的flex-basis,以便从总可用空间(flex 容器的宽度)中减去可用分配空间,然后剩余空间在每个 flex 子元素之间平均分配。我们比较大的 flex 子元素最终会变得更大,因为它一开始就有一个比较大的尺寸,即使它与其他 flex 子元素具有相同数量的分配空间:

如果你真正想要的是三个同样尺寸的 flex 子元素,即使它们开始是不同的尺寸,你应该使用这个:

flex: 1 1 0;

我们要说的是,为了我们的空间分配,flex 子元素的尺寸计算值是 0–所有空间都用来争夺,并且所有 flex 子元素具有相同的flex-grow 值,它们(flex 子元素)每个都获得相等的空间分配。最终结果是三个宽度相等的可伸缩 flex 子元素。

flex items设置不同的flex-grow值

我们对flex-grow和flex-basis如何工作的理解使我们能够通过分配不同的flex-grow值来进一步控制我们单个 flex 子元素 大小。如果我们设置flex-basis值为0则所用空间都可以被分配,我们可以给每个 flex 子元素分配不同的flex-grow值。在下面的示例中,我会使用下面的值:

  • 设置第一个 flex item 的flex-grow值为1.
  • 设置第二个 flex item 的flex-grow值为1.
  • 设置第三个 flex item 的flex-grow值为2.

flex-basis值为 0 意味着可用空间会根据设置分配。我们需要增加 flex 增长值,就需要在 flex 容器中用 positive free space 的总大小除以 flex-grow 值之和,在这个例子中为 4. 我们就可以根据个体值(flex-basis 值)分配空间 — 第一个 flex 子元素得到一个单位 , 第二个 flex 子元素得到一个单位,第三个 flex 子元素得到二个单位。也就是说第三个 flex 子元素是第一个和第二个 flex item 的两倍。

flex-shrink属性

flex-shrink 属性指定了flex 缩小值, 它确定在分配 negative free space 时,flex 子元素相对于 flex 容器中其余 flex 子元素收缩的程度。

该属性用于处理浏览器计算 flex 子元素的flex-basis值的情形,并检测它们太大以至于无法适应 flex 容器。只要 flex-shrink 有正值则 flex 子元素就会收缩以至于它们不会溢出 flex 容器

因此 flex-grow 用于添加可用空间,flex-shrink 减少空间来使盒子适应它们的容器而不溢出。

一个示例中我的 flex 容器有三个 flex 子元素,我已经给它们每一个(flex 子元素)设置了 200px 的宽度,并且设置容器 (flex 容器)500px 宽。设置flex-shrink 为 0 的 flex 子元素 不允许收缩以致于它们溢出了盒子。

改变 flex-shrink 值为 1 你会发现每个 flex 子元素 都收缩了同样大小的量,现在所有 flex 子元素都适应盒子。这样做后它们已变得比它们的初始宽度还小。

结合flex-shrink和flex-basis

你可以看见 flex-shrink和flex-grow 工作的一样好。但是它们有两个原因使它们不完全一样。

即使它微不足道,但规范中的定义内容是 flex-shrink不全相同与 negative space 的一个原因,正如flex-grow不全相同于 positive space 一样:

当分配 negative space 时,flex 基本大小乘以 flex 增长值。这会根据 flex 子元素能够缩小的比例(多少)分配 negative space, 其它亦如此。在较大的 flex 子元素 明显缩小之前,较小的 flex 子元素 不会缩小到 0.”

第二个原因是在 negative free space 消除期间 flexbox 会阻止小的 flex 子元素缩小到 0. 这些 flex 子元素会以min-content的大小进行铺设 —这个大小是它们利用任何可以利用的自动断行机会后所变成的。

在下面的例子中,在 flex-basis 解析为内容大小的位置你会看到 min-content 的铺设。如果你改变 flex 容器的宽度——比如增加到 700px 宽,再减少 flex 子元素的宽度,你会看到前两个 flex 子元素将换行,但是它们绝不会小于 min-content 的大小。随着盒子变得越来越小,第三个 flex 子元素随后从空间中溢出。

1
2
3
4
5
6
7
8
.box {
width: 500px;
display: flex;
}

.box > * {
flex: 1 1 auto;
}
1
2
3
4
5
<div class="box">
<div>Item One</div>
<div>Item Two</div>
<div>Item Three has more content and so has a larger size</div>
</div>

掌握 Flex items 的大小

真正理解 flex 子元素如何工作的关键是理解有多少东西参与影响 flex 子元素。思考以下方面,哪些是我们在这些指南中已经讨论过了的:

什么设置 flex item 的基本大小?

  1. lex-basis 设置为auto吗,这个 flex 子元素设置了宽度吗?如果设置了,flex 子元素的大小将会基于设置的宽度。
  2. flex-basis 设为 auto 还是content (在支持的浏览器中)? 如果是auto, flex 子元素的大小为原始大小。
  3. flex-basis 是不为0的长度单位吗?如果是这样那这就是 flex 子元素的大小。
  4. flex-basis 设为 0呢?如果是这样,则 flex 子元素的大小不在空间分配计算的考虑之内。

我们有可用空间吗?

flex 子元素没有 positive free space 就不会增长,没有 negative free space 就不会缩小。

  1. 如果我们把所有的 flex 子元素的宽度相加(如果在列方向工作则为高度),那么总和是否小于 flex 容器的总宽度(或高度)?如果是这样,那么你有 positive free space,并且 flex-grow 会发挥作用。
  2. 如果我们把所有的 flex 子元素的宽度相加(如果在列方向工作则为高度),那么总和是否大于 flex 容器的总宽度(或高度)?如果是这样,那么你有 negative free space,并且 flex-shrink 会发挥作用。

分配空间的其他方式

如果我们不想空间添加到 flex 子元素中,记住你可以在 flex 容器中使用指南中所描述的对准属性来处理 flex 子元素之间或者 flex 子元素周围的空闲空间,以致可以对齐 flex 子元素。justify-content 属性能够在 flex 子元素之间或 flex 子元素周围分配空闲空间。您还可以在 flex 子元素上使用自动边距 (auto margins) 来吸收空间并在 flex 子元素之间创建间距。

参考文章:

flex布局基本概念
控制Flex子元素在主轴上的比例