【CSS】Flex布局(包含flex-grow和flex-shrink的详细计算方式)

3,027 阅读9分钟

参考

介绍

传统的布局方案,基于盒模型,依赖display属性、position属性和float属性。其对于某些特殊的布局(如垂直居中),实现起来比较麻烦。

在 2009 年,W3C 提出了一种新的布局方案——Flex 布局(Flexible Box 布局,弹性盒子布局),其相比于传统的布局方案,更为灵活和简便。目前,Flex 布局已经得到了所有浏览器的支持。

将元素的display属性设置为flexinline-flex后,即可开启 Flex 布局:

// 块级元素
.block-box {
  display: flex;
}

// 行内元素
.inline-box {
  display: inline-flex;
}

同时,该元素会自动成为 Flex 容器,简称容器。且容器的所有子元素会自动成为 Flex 容器成员,简称项目。项目的floatclearvertical-align属性将失效。

容器中默认存在两根轴:主轴侧轴。主轴和侧轴互相垂直,类似于平面坐标系中的 x 轴和 y 轴。项目将自动沿着主轴方向排列,排满时将沿侧轴方向堆砌,即在侧轴方向上换行(前提是容器flex-wrap属性不为nowrap)。

调试

可使用Flex 布局在线调试工具来快速配置 Flex 布局和查看 Flex 布局效果:

对于该工具的介绍请见此文

容器的属性

flex-direction

flex-direction属性决定主轴的方向,进而决定项目的排列方向,其值可能为:

.container {
  // 主轴方向从左向右→(默认值)
  flex-direction: row;

  // 主轴方向从右向左←
  flex-direction: row-reverse;

  // 主轴方向从上向下↓
  flex-direction: column;

  // 主轴方向从下向上↑
  flex-direction: column-reverse;
}

flex-wrap

flex-wrap属性决定侧轴的方向,进而决定项目的换行方向,其值可能为:

.container {
  // 侧轴无方向(默认值)
  // 相当于不换行
  flex-wrap: nowrap;

  // 主轴方向水平时,侧轴方向从上向下↓
  // 主轴方向竖直时,侧轴方向从左向右→
  // 相当于顺序换行
  flex-wrap: wrap;

  // 主轴方向水平时,侧轴方向从下向上↑
  // 主轴方向竖直时,侧轴方向从右向左←
  // 相当于逆序换行
  flex-wrap: wrap-reverse;
}

flex-flow

flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,其值可能为:

.container {
  // 主轴方向从左向右,侧轴无方向(默认值)
  // 即项目排列方向→,不换行
  flex-flow: row nowrap;

  // 主轴方向从左向右,侧轴方向从上向下
  // 即项目排列方向→,换行方向↓,与现代书写方向相同
  flex-flow: row wrap;

  // 主轴方向从左向右,侧轴方向从下向上
  // 即项目排列方向→,换行方向↑
  flex-flow: row wrap-reverse;

  // 主轴方向从右向左,侧轴无方向
  // 即项目排列方向←,不换行
  flex-flow: row-reverse nowrap;

  // 主轴方向从右向左,侧轴方向从上向下
  // 即项目排列方向←,换行方向↓
  flex-flow: row-reverse wrap;

  // 主轴方向从右向左,侧轴方向从下向上
  // 即项目排列方向←,换行方向↑
  flex-flow: row-reverse wrap-reverse;

  // 主轴方向从上向下,侧轴无方向
  // 即项目排列方向↓,不换行
  flex-flow: column nowrap;

  // 主轴方向从上向下,侧轴方向从左向右
  // 即项目排列方向↓,换行方向→
  flex-flow: column wrap;

  // 主轴方向从上向下,侧轴方向从右向左
  // 即项目排列方向↓,换行方向←,与中国古代书写方向相同
  flex-flow: column wrap-reverse;

  // 主轴方向从下向上,侧轴无方向
  // 即项目排列方向↑,不换行
  flex-flow: column-reverse nowrap;

  // 主轴方向从下向上,侧轴方向从左向右
  // 即项目排列方向↑,换行方向→
  flex-flow: column-reverse wrap;

  // 主轴方向从下向上,侧轴方向从右向左
  // 即项目排列方向↑,换行方向←
  flex-flow: column-reverse wrap-reverse;
}

可基于以下配置对flex-flow的值进行修改,查看其不同值的对应效果,以验证上述规律:

.container {
  width: 200px;
  height: 200px;
  flex-flow: <flex-direction> | <flex-wrap>;
  .item-1,
  .item-2,
  .item-3 {
    width: 100px;
    height: 100px;
  }
}

justify-content

justify-content属性决定了项目沿主轴方向的对齐方式,其值可能为:

.container {
  // 主轴方向上,起点对齐(默认值)
  justify-content: flex-start;

  // 主轴方向上,终点对齐
  justify-content: flex-end;

  // 主轴方向上,居中对齐
  justify-content: center;

  // 主轴方向上,项目之间空隙相等
  justify-content: space-between;

  // 主轴方向上,项目两侧空隙相等
  justify-content: space-around;
}

align-content

align-content属性决定了项目沿侧轴方向的对齐方式(如果项目只有一行,则该属性无效),其值可能为:

.container {
  // 如果项目height未设置或为auto
  // 项目拉伸至占满侧轴(默认值)
  align-content: stretch;

  // 侧轴方向上,起点对齐
  align-content: flex-start;

  // 侧轴方向上,终点对齐
  align-content: flex-end;

  // 侧轴方向上,居中对齐
  align-content: center;

  // 侧轴方向上,项目之间空隙相等
  align-content: space-between;

  // 侧轴方向上,项目两侧空隙相等
  align-content: space-around;
}

place-content

place-content属性是align-content属性和justify-content属性的简写形式,其可能值见此文

.container {
  place-content: <align-content> | <justify-content>;
}

align-items

align-items属性决定了同一行项目沿侧轴方向的对齐方式,即项目在主轴方向上的垂直对齐方式,其值可能为:

.container {
  // 如果项目height未设置或为auto
  // 则拉伸至占满该行(默认值)
  align-items: stretch;

  // 侧轴方向上,起点对齐
  align-items: flex-start;

  // 侧轴方向上,终点对齐
  align-items: flex-end;

  // 侧轴方向上,居中对齐
  align-items: center;

  // 侧轴方向上,基线对齐
  align-items: baseline;
}

项目的属性

order

order属性决定了项目沿主轴方向的排列顺序,其值可能为:

.item {
  // 数值越小,项目排列越靠前(默认值为1)
  order: <integer>;
}

align-self

align-self属性会覆盖容器的align-items属性,其值可能为:

.item {
  // 继承容器的align-items属性(默认值)
  align-self: auto;

  align-self: stretch;

  align-self: flex-start;

  align-self: flex-end;

  align-self: center;

  align-self: baseline;
}

flex-basis

flex-basis属性会覆盖项目的width属性(当主轴方向水平时)或height属性(当主轴方向垂直时),下文中的flex-growflex-shrink都基于flex-basis进行计算,其值可能为:

.item {
  // 设置为与width或height相同的值(默认值)
  flex-basis: auto;

  flex-basis: <length>;
}

然而,flex-basis属性仍然受max-width/max-heightmin-width/min-height属性的约束。

flex-grow

flex-grow属性决定了项目沿主轴方向的伸长系数,只在容器有剩余空间时有效,其值可能为:

.item {
  // (默认值为0)
  flex-grow: <number>;
}

关于flex-grow属性的计算过程为:

  1. flex-grow属性小于等于0的项目不受影响,不会伸长。
  2. 对剩余项目的flex-grow属性进行求和,将结果记为sum
  3. 分以下情况进行讨论。

sum ≥ 1 时

.container {
  display: flex;
  width: 800px;
  .item1 {
    flex-basis: 50px;
  }
  .item2 {
    flex-basis: 100px;
    flex-grow: 1;
  }
  .item3 {
    flex-basis: 150px;
    flex-grow: 3;
  }
  .item4 {
    flex-basis: 200px;
    flex-grow: 6;
  }
}

计算过程:

  1. 计算剩余宽度:剩余宽度 = 容器宽度 - 项目总宽度,所以:
    • 剩余宽度 = 800px - 500px = 300px
  2. 计算各项目权重占比:权重 = <flex-grow>,所以:
    • 总权重 = sum = 1 + 3 + 6 = 10
    • 项目2权重 = 1项目2权重占比 = 1 / 10 = 0.1
    • 项目3权重 = 3项目3权重占比 = 3 / 10 = 0.3
    • 项目4权重 = 6项目4权重占比 = 6 / 10 = 0.6
  3. 计算各项目伸长宽度:伸长宽度 = 权重占比 * 剩余宽度,所以:
    • 项目2伸长宽度 = 0.1 * 300px = 30px
    • 项目3伸长宽度 = 0.3 * 300px = 90px
    • 项目4伸长宽度 = 0.6 * 300px = 180px
  4. 计算各项目伸长后宽度:伸长后宽度 = <flex-basis> + 伸长宽度,所以:
    • 项目2伸长后宽度 = 100px + 30px = 130px
    • 项目3伸长后宽度 = 150px + 90px = 240px
    • 项目4伸长后宽度 = 200px + 180px = 380px

sum < 1 时

计算过程基本与sum > 1时的情况相同,但在第 3 步中,各项目的伸长宽度为sum * 权重占比 * 剩余宽度

flex-shrink

flex-shrink属性决定了项目沿主轴方向的缩短系数,只在容器空间发生溢出时有效,其值可能为:

.item {
  // (默认值为1)
  flex-shrink: <number>;
}

关于flex-shrink属性的计算过程为:

  1. flex-shrink属性小于等于0的项目不受影响,不会缩短。
  2. 对剩余项目的flex-shrink属性进行求和,将结果记为sum
  3. 分以下情况进行讨论。

sum ≥ 1 时

.container {
  display: flex;
  width: 800px;
  .item1 {
    flex-basis: 100px;
  }
  .item2 {
    flex-basis: 200px;
    flex-shrink: 6;
  }
  .item3 {
    flex-basis: 300px;
    flex-shrink: 3;
  }
  .item4 {
    flex-basis: 400px;
    flex-shrink: 1;
  }
}

计算过程:

  1. 计算溢出宽度:溢出宽度 = 项目总宽度 - 容器宽度,所以:
    • 溢出宽度 = 800px - 1000px = 200px
  2. 计算各项目权重占比:权重 = <flex-shrink> * <flex-basis>,所以:
    • 总权重 = 1200 + 900 + 400 = 2500
    • 项目2权重 = 1200项目2权重占比 = 1200 / 2500 = 0.48
    • 项目3权重 = 900项目3权重占比 = 900 / 2500 = 0.36
    • 项目4权重 = 400项目4权重占比 = 400 / 2500 = 0.16
  3. 计算各项目缩短宽度:缩短宽度 = 权重占比 * 溢出宽度,所以:
    • 项目2伸长宽度 = 0.48 * 200px = 96px
    • 项目3伸长宽度 = 0.36 * 200px = 72px
    • 项目4伸长宽度 = 0.16 * 200px = 32px
  4. 计算各项目缩短后宽度:缩短后宽度 = 原始宽度 - 缩短宽度,所以:
    • 项目2缩短后宽度 = 200px - 96px = 104px
    • 项目3缩短后宽度 = 300px - 72px = 228px
    • 项目4缩短后宽度 = 400px - 32px = 368px

sum < 1 时

计算过程基本与sum > 1时的情况相同,但在第 3 步中,各项目的缩短宽度为sum * 权重占比 * 溢出宽度

flex

flex属性是flex-grow属性、flex-shrinkflex-basis属性的简写形式,其值可能为:

.item {
  // 相当于flex:0 0 auto;
  flex: none;

  // 相当于flex:1 1 auto;
  flex: auto;

  flex: <flex-grow> <flex-shrink> <flex-basis>;
}