详解flex布局的元素如何分配容器的剩余空间

文章主要介绍了详解flex布局的元素如何分配容器的剩余空间,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

自从开始开学习 CSS 布局,想要比较灵活的把父元素的空间分配给各个子元一直是各个前端程序员的梦想。

在 flex 之前,如果不是专门去搜索相关的解决方案,一般人几乎想不出非常灵活的三(多)栏等高布局方案,而即使看了解决方案,很多人也会大呼奇技淫巧。

不得不感慨在 flex 之前 CSS 的布局功能之弱:基本只能使用一些并非为布局而设计的属性来实现想要的布局——float、inline-block、position、甚至是 table 等。而使用这些属性来实现各种布局效果,往往又会遇到相当多另外的坑:比如浮动的闭合、inline-block 的垂直对齐、position 的定位原点以及 table 的不够灵活等。

直到出现了 flex

flex 可以说是一次性解决了前端布局的所有的问题(当然,并没有完全解决,要不然也不会有 grid layout 了),以前很难实现的布局效果在 flex 下简直不能更简单,以至于一些其它平台也开始吸纳 flex 的布局思想,也有些开源项目把 flex 的布局方式移植到其它平台。

中文社区也有不少写 flex 的文章,比如 ruanyifeng。然而个人觉得不少写 flex 的文章都有个通病,就是一上来就整一堆 flex 相关的术语,比如 flex container,flex item,main axis(主轴),cors axis(交叉轴),不禁让人望而生畏,都还没搞清楚怎么回事,就来一堆术语。

然而这还不是最大的问题,最大的问题是很多文章并没有把 flex 布局的详细计算方式讲清楚,尤其是连 ruanyifeng 的文章也没把这事说清楚,但是在 Google 搜索 flex 相关的文章,他的文章却会出现在第一页。因为我觉得他写的并不好,所以就不贴地址了,想看的同学可以自己搜一下,就在第一页。

即使是 MDN 以及《The Book Of CSS3》里也没把 flex-grow 和 flex-shrink 的计算方式说清楚。

所以我决定写这一篇文章,把 flex-grow 与 flex-shrink 的详细计算方式讲清楚。

flex 如何解决传统常见布局问题

在传统布局中最常见也是急需的当然就是在从左往右把父元素的空间分配给子元素以实现多栏布局了:按比例也好,定宽也好,更灵活的定宽加占用剩余空间也好。

那我们就从使用 flex 如何实现三栏布局开始吧。

想要实现三栏等高布局,且两边的侧栏宽度固定而中间一栏占用剩余的空间,如下代码就足够了:

<style>
  section {display: flex;}
  .left-side,
  .right-side {width: 200px;}
  .content {flex-grow: 1;}
</style>
<section>
  <div class="left-side"></div>
  <div class="content"></div>
  <div class="right-side"></div>
</section>

其中 section 元素的宽度将会像 block 元素一样尽量的宽,对外面的元素来说,它的行为很像一个 block 块。三个元素会从左往右占据父元素的空间(这很显然)。左右侧边栏的宽度都是 200px,中间 .content 元素的宽度将会占据 section 元素的剩余宽度。

另外,section 的高度会自动被最高的一个子元素撑开,同时其它子元素的高度也会被拉到跟 section 元素一样高,而如果给 section 元素设置了高度,而所有子元素的高度设置为 auto ,所有的子元素也都会自动跟父元素一样高,这简直就是在传统布局中做梦都想要的功能!

总之,在高度方面,flex 的表现是相当符合直觉的。

另外,如果不给 flex 子元素设置宽度和 flex-grow,它会尽量的窄。

flex-grow 的计算方式

上面 demo 中最值得注意的是 .content 元素的 flex-grow 属性,设置为 1 它就可以占满水平剩余空间。这也是本文的重点:讲清 flex-grow 与 flex-shrink 属性的详细计算方式。

flex-grow 属性决定了父元素在空间分配方向上还有剩余空间时,如何分配这些剩余空间。其值为一个权重(也称扩张因子),默认为 0(纯数值,无单位),剩余空间将会按照这个权重来分配。

比如剩余空间为 x,三个元素的 flex-grow 分别为 a,b,c。设 sum 为 a + b + c。那么三个元素将得到剩余空间分别是 x a / sum, x b / sum, x * c / sum,是为权重也。

举个例子:

父元素宽度 500px,三个子元素的 width 分别为 100px,150px,100px。

于是剩余空间为 150px

三个元素的 flex-grow 分别是 1,2,3,于是 sum 为 6
则三个元素所得到的多余空间分别是:

150 * 1 / 6 = 25px
150 * 2 / 6 = 50px
150 * 3 / 6 = 75px
三个元素最终的宽度分别为 125px,200px,175px。

100px + 25px = 125px
150px + 50px = 200px
100px + 75px = 175px
可以打开这个 demo(下文中所有的 demo 都在这个页面) 然后用开发工具查看一下。注意不要用截图工具量,可能量不准,因为高分屏和放大等诸多因素都会影响测量结果。

然而!不止这些,还有一种情况:

当所有元素的 flex-grow 之和小于 1 的时候(注意是 1,也就是说每个元素的 flex-grow 都是一个小数如 0.2 这样的),上面式子中的 sum 将会使用 1 来参与计算,而不论它们的和是多少。也就是说,当所有的元素的 flex-grow 之和小于 1 的时候,剩余空间不会全部分配给各个元素。

实际上用来分配的空间是 sum(flex-grow) / 1 * 剩余空间,这些用来分配的空间依然是按 flex-grow 的比例来分配。

还是上面一个例子,但是三个元素的 flex-grow 分别是 0.1,0.2,0.3,那么计算公式将变成下面这样:

150 * 0.1 / 1 = 15px
150 * 0.2 / 1 = 30px
150 * 0.3 / 1 = 45px
150px - 15px - 30px - 45px = 60px,即还有 60px 没有分配给任何子元素。

三个元素的最终宽度分别为:

100px + 15px = 115px
150px + 30px = 180px
100px + 45px = 145px

如上所述即是 flex-grow 的计算方式。

另外,flex-grow 还会受到 max-width 的影响。如果最终 grow 后的结果大于 max-width 指定的值,max-width 的值将会优先使用。同样会导致父元素有部分剩余空间没有分配。

flex-shrink 的计算方式

前文已经说到,flex 几乎一次性解决了前端布局的所有问题。

那么既然可以在空间有多余时把多余空间分配给各个子元素,当然也可以在空间不够时让各个子元素收缩以适应有限的空间了。

这就是 flex-shrink 属性的作用。

你可能会觉得 flex-shrink 的计算方式跟 flex-grow 很类似,然而事情并没有这么简单。

flex-shrink 属性定义空间不够时各个元素如何收缩。其值默认为 1。很多文章对此基本是一笔带过:“flex-shrink 属性定义了元素的收缩系数”,根本就不说它具体是怎么计算的。

flex-shrink 定义的仅仅只是元素宽度变小的一个权重分量。

每个元素具体收缩多少,还有另一个重要因素,即它本身的宽度。

举个例子:

父元素 500px。三个子元素分别设置为 150px,200px,300px。

三个子元素的 flex-shrink 的值分别为 1,2,3。

首先,计算子元素溢出多少:150 + 200 + 300 - 500 = -150px。

那这 -150px 将由三个元素的分别收缩一定的量来弥补。

具体的计算方式为:每个元素收缩的权重为其 flex-shrink 乘以其宽度。

所以总权重为 1 150 + 2 200 + 3 * 300 = 1450

三个元素分别收缩:

150 1(flex-shrink) 150(width) / 1450 = -15.5
150 2(flex-shrink) 200(width) / 1450 = -41.4
150 3(flex-shrink) 300(width) / 1450 = -93.1

三个元素的最终宽度分别为:

150 - 15.5 = 134.5
200 - 41.4 = 158.6
300 - 93.1 = 206.9

同样,当所有元素的 flex-shrink 之和小于 1 时,计算方式也会有所不同:

此时,并不会收缩所有的空间,而只会收缩 flex-shrink 之和相对于 1 的比例的空间。

还是上面的例子,但是 flex-shrink 分别改为 0.1,0.2,0.3。

于是总权重为 145(正好缩小 10 倍,略去计算公式)。

三个元素收缩总和并不是 150px,而是只会收缩 150px 的 (0.1 + 0.2 + 0.3) / 1 即 60% 的空间:90px。

每个元素收缩的空间为:

90 0.1(flex-shrink) 150(width) / 145 = 9.31
90 0.2(flex-shrink) 200(width) / 145 = 24.83
90 0.3(flex-shrink) 300(width) / 145 = 55.86

三个元素的最终宽度分别为:

150 - 9.31 = 140.69
200 - 24.83 = 175.17
300 - 55.86 = 244.14

当然,类似 flex-grow,flex-shrink 也会受到 min-width 的影响。

总结

虽然上面的公式看起来很复杂,其实计算过程还是比较简单的:如果所有元素的 flex-grow/shrink 之和大于等于 1,则所有子元素的尺寸一定会被调整到适应父元素的尺寸(在不考虑 max/min-width/height 的前提下),而如果 flex-grow/shrink 之和小于 1,则只会 grow 或 shrink 所有元素 flex-grow/shrink 之和相对于 1 的比例。grow 时的每个元素的权重即为元素的 flex-grow 的值;shrink 时每个元素的权重则为元素 flex-shrink 乘以 width 后的值。