flex 宽度计算

1,310 阅读4分钟

最近看到一些关于flex的面试题,发现其中关于宽度计算的问题比较多,网上的解释也有些不太完整,于是自己总结了一下。若对 flex 不是很了解,可以看一下阮一峰老师的教程 Flex 布局教程:语法篇

flex 属性值

一般来说,使用 flex 时都是作为简写方式,类似于 background 属性可以包含 background-image background-repeat 等属性。flex 属性是 flex-grow , flex-shrinkflex-basis 的简写,默认值为 0 1 auto 。后两个属性可选。其中:

  • flex-grow 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大;
  • flex-shrink 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小;
  • flex-basis 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为 auto ,即项目的本来大小。

flex 的属性介绍可以查看MDN,以下是部分摘抄:

flex 属性可以指定1个,2个或3个值。

单值语法: 值必须为以下其中之一:

  • 一个无单位数( <number> ): 它会被当作 <flex-grow> 的值。
  • 一个有效的宽度(width)值: 它会被当作 <flex-basis> 的值。
  • 关键字 noneautoinitial .

双值语法: 第一个值必须为一个无单位数,并且它会被当作 <flex-grow> 的值。第二个值必须为以下之一:

  • 一个无单位数:它会被当作 <flex-shrink> 的值。
  • 一个有效的宽度值: 它会被当作 <flex-basis> 的值。

三值语法:

  • 第一个值必须为一个无单位数,并且它会被当作 <flex-grow> 的值。
  • 第二个值必须为一个无单位数,并且它会被当作 <flex-shrink> 的值。
  • 第三个值必须为一个有效的宽度值, 并且它会被当作 <flex-basis> 的值。

涉及到flex的宽度计算有两种情况:

  1. 主轴有多余空间分配,需要给子项目增加宽度(或高度)
  2. 主轴没有多余空间分配,并且空间不足以支持所有项目的宽度,需要子项目 挤出 空间

有多余空间分配

举个栗子:

<div class="container">
    <div class="left"></div>
    <div class="center"></div>
    <div class="right"></div>
</div>

<style>
  * {
    padding: 0;
    margin: 0;
  }
  .container {
    width: 600px;
    height: 300px;
    display: flex;
  }
  .left {
    width: 120px;
    flex: 1 2 140px;
    background: red;
  }
  .center {
    width: 100px;
    flex: 2 1 auto;
    background: yellow;
  }
  .right {
    flex: 2 1 200px;
    background: blue;
  }
</style>

主轴上空器宽度为600px,子元素总基准值为:140px + 100px + 200px = 440px . 所以此时主轴上是有剩余空间可以分配的,剩余空间为 600px - 440px = 160px

注:MDN上有说明,当一个元素同时设置了 flex-basiswidth 时,flex-basis 具有更高的优先级。所以上述计算 left 的宽度以 140 计算而不是 120

flex-grow 系数之和为: 1 + 2 + 2 = 5

所以各子项目额外分配的剩余空间为

  • left: 160 * 1/5 = 32
  • center: 160 * 2/5 = 64
  • right: 160 * 2/5 = 64

最终各项目的宽度为:

  • left: 140 + 32 = 172
  • center: 100 + 64 = 164
  • right: 200 + 64 = 264

主轴空间不足

话不多说,上代码:

<div class="container">
    <div class="left"></div>
    <div class="right"></div>
</div>

<style>
  * {
    padding: 0;
    margin: 0;
  }
  .container {
    width: 600px;
    height: 300px;
    display: flex;
  }
  .left {
    flex: 1 2 500px;
    background: red;
  }
  .right {
    flex: 2 1 400px;
    background: blue;
  }
</style>

问:left、right 最终渲染时的宽度是多少?

首先容器宽度是600px,两个子项目的宽度以 flex-basis 计算为 500px + 400px = 900px , 很显然容器的空间是不足以完全放下两个子项目的。这个时候就需要 “缩减” 子项目的宽度来 “补偿” 不足的 300px (900px - 600px) 空间。

其次,当空间不足时,我们可以忽略 flex-grow 属性,因为该属性是在主轴有剩余空间时才起作用。

那如何 “补偿” 300px的空间呢:

  • 每个项目的 flex-basis * flex-shrink

    • left: 500 * 2 = 1000
    • right: 400 * 1 = 400
  • 求和

    • 1000 + 400 = 1400
  • 求得每个项目需要腾出的空间。

    • left: (1000 / 1400) * 300 = 214.29
    • right: (400 / 1400) * 300 = 85.71
  • 子项目减去腾出的空间即为最终宽度

    • left: 500 - 214.29 = 285.71
    • right: 400 - 85.71 = 314.29

所以最终渲染结果为 left 宽度为 285.71px, right 宽度为 314.29px

总结

在计算 flex 子项目的宽度时不要被 flex-basis 的值干扰了。flex-basis 首先是要被用来计算父容器是否有剩余空间可以被分配,然后再根据有无剩余空间取 flex-growflex-shrink 的值进行计算,最后再以 flex-basis 为宽度求和得到最后结果。