Flexbox布局中不为人知的细节

发布于 大漠

Flexbox布局 已是目前最为流行的Web布局方式之一,它给Web开发者在完成页面或组件的UI布局带来了极大的灵活性和便利性。但也是因为它有极大的灵活性,里面隐藏了一些不为人知的细节,如果不是对Flexbox极为熟悉或者对其规范极为了解的话,其中有很多细节将会被遗漏,而这些细节又会让你在使用的感到困惑,甚至是带来一定的麻烦。

这次在优化imgcook的Flexbox布局时,重新阅读了一次Flexbox的规范,发现自己曾忽略了部分重要信息。为此在这篇文章中,将Flexbox布局CSS的书写模式逻辑属性,对齐方式结合在一起整理了一篇笔记,希望对于想了解或使用Flexbox碰到痛楚的同学有所帮助。

一些术语和概念

Flexbox术语

术语的统一有助于我们后面更好的讨论和解决问题。用下图来描述Flexbox中的术语:

Flexbox术语

主轴和侧轴只有在Flexbox布局体系中才有这样的概念,并不是水平方向永远都是主轴(Main Axis),垂直方向永远是侧轴(Cross Axis)。主轴和侧轴除了会受Flexbox中的 flex-direction 取值的影响之外,还会受CSS的书写模式 writing-mode 和 direction 以及HTML的 dir 属性的影响!

块轴和内联轴

CSS Box Alignment Module Level 3 引入了两个新的概念,即 **块轴(Block Axis)**和 内联轴(Inline Axis) :

  • 块轴 是沿 块(block) (比如段落元素)的布局方向延伸的轴, 它会垂直穿过行内轴 
  • 内联轴 是在使用特定写作模式中,沿句子单词的流动方向的轴。比如对于英语或者中文来说, 内联轴是水平的

 

块轴和内联轴

同时 块轴(Block Axis)又常称为列(Column), 内联轴(Inline Axis)又常称为行(Row):

块轴和内联轴 

虽然目前为止在 Flexbox规范和Grid规范中都有自身关于对齐方式的描述,但CSS中有关于对齐方式都将收口到Box Alignment 模块中;为此后面对说轴的说法更多的是“ 块轴 ” 和 “ 行内轴 ”,结合到Flexbox布局中,轴的对应关系是:

  • 行内轴(Inline Axis)也对标 Flexbox中的主轴 Main Axis
  • 块轴(Block Axis)也对标 Flexbox中的侧轴 Cross Axis

块轴和行内轴同样受CSS的书写模式 writing-mode 和 direction 以及HTML的dir 属性影响。只不过,在Flexbox布局中,还受 flex-direction 属性的影响

书写模式

CSS Writing Modes Level 3 规范中的 writing-mode 和 direction 以及HTML中的 dir 属性对于Flexbox中的主轴和侧轴都会有影响,将会改变主轴和侧轴的方向。

书写模式

书写模式

逻辑属性

块轴 、 内联轴 、 书写模式 的出现之后,就有了 块起点(Block Start)、块终点(Block End)、内联起点(Inline Start)和 内联终点(Inline End): 

逻辑属性

如果放到Flexbox布局中:

  • 内联起点(Inline Start) 等同于 Flexbox布局中的 主轴起点(Main Start) 
  • 内联终点(Inline End) 等同于 Flexbox布局中的 主轴终点(Main End) 
  • 块起点(Block Start) 等同于 Flexbox布局中的 侧轴起点(Cross Start) 
  • 块终点(Block End) 等同于 Flexbox布局中的 侧轴终点(Cross End)

 

同时 CSS Logical Properties and Values Level 1 规范中引入了 block-start 、 block-end 、 inline-start 和 inline-end , 但它们和Flexbox中,Grid中以入Box Alignment中的 flex-start 、 start 、 flex-end 和 end 是不等同的,也不是同一领域中的概念。这几个属性对应的是物理属性中的 top 、 right 、 bottom 和 left :

逻辑属性

也就是说,引入CSS逻辑属性之后,CSS盒模型将分 物理盒模型 和 逻辑盒模型 :

逻辑属性

CSS属性也从此之后有 逻辑属性 和 物理属性 之分:

逻辑属性

注意,CSS逻辑属性也受CSS书写模式 writing-mode 、 directioin 属性和HTML的 dir 属性影响,而且不同组合之下也不同:

逻辑属性

剩余空间(可用空间)和 不足空间

在Flexbox布局模块中,Flex容器中可能会包含一个或多个Flex项目。而Flex容器和Flex项目都有其自身的尺寸大小,那么就会有Flex项目尺寸大小之和大于或小于Flex容器的情景:

  • 当所有Flex项目尺寸大小之和小于Flex容器时,Flex容器就会有多余的空间没有被填充,那么这个空间就被称为 Flex容器的剩余空间(Positive Free Space)
  • 当所有Flex项目尺寸大小之和大于Flex容器时,Flex容器就没有足够的空间容纳所有Flex项目(Flex项目会溢出Flex容器),那么多出来的这个空间就被称为不足空间(Negative Free Space),也被称为负空间

剩余空间(可用空间)和 不足空间

剩余空间(可用空间)和 不足空间

Flex容器 和 Flex项目

在元素上使用 display 设置值为 flex 或 inline-flex ,该容器会成为 Flex容器,该容器下的子元素,包括 文本节点,伪元素。

Flex容器 和 Flex项目

使用 flex 和 inline-flex 的具体场景:

Flex容器 和 Flex项目

如果元素显式设置了 display 的值为 flex 或 inline-flex ,Flex项目在未显式设置与尺寸大小有关的属性时,Flex项目都将会按其内容大小来计算自身大小。

  • 设置为 display: flex 时,Flex容器未显式设置与宽度相关的属性时,其宽度与其父容器等同(相当于 width: 100% )
  • 设置为 display: inline-flex 时,Flex容器未显式设置与宽度相关的属性时,其宽度等同于所有Flex项目的宽度和

Flex容器 和 Flex项目

当Flex容器中所有Flex项目所有宽和大于Flex容器时:

  • 设置为 display: flex 时,Flex项目会溢出Flex容器
  • 设置为 display: inline-flex 时,Flex项目会撑大Flex容器,有可能造成Flex容器溢出其父元素(或祖先元素)

Flex容器 和 Flex项目

使用 display: inline-flex 时最好结合 min-width 和 min-height 一起使用。不建议显式设置 width 和 height 。

display 设置为 flex 时,Flex容器从表现形式上类似于块容器,事实它是一个Flex容器,上下文格式是FFC(Flexbox Formatting Content),因此运用于块容器(Block Formatting Content)上的一些布局属性就不再适用,比如:

  • CSS的 column-*  属性在Flex容器上不起作用
  • CSS的 float  和 clear 属性在Flex项目上不起作用,也不会让Flex项目脱离文档流
  • CSS的 vertical-align 属性在Flex项目上不起作用
  • CSS伪元素 ::first-line  和 ::first-letter 在Flex容器上不起作用,而且Flex容器不会为其祖先提供首行或首字母格式化

有一点需要注意, 如果元素的 display的值为 inline-flex ,并且该元素显式的设置了 float 或 position 的值为 relativeabsolute 或 fixed,那么 display 的计算值是 flex, 即 Flex容器表现行为和 display: flex 等同 。

运用于Flex容器上的属性

运用于Flex容器上的属性

指定主轴方向

在Flex容器中显式使用 flex-direction  可以指定主轴方向,如果未显式设置 flex-direction 属性,Flex容器则会采用其默认值 row 。

指定主轴方向

上图展示的仅是阅读方式是 LTR(Left-to-Right),如无特殊声明,接下来的文档不会因阅读方式(即CSS的 writing-mode 、 direction 和HTML的 dir 属性)列出不同的示意图。

除非需要显式的修改主轴方向,才需要在Flex容器上显式设置 flex-direction , 比如像下图这种排版本方式:

 指定主轴方向

row-reverse 和默认值 row 表现恰恰相反,适用于下面这样布局场景:

指定主轴方向

在Flexbox布局中, flex-direction 在指定Flex容器主轴方向时,也会对Flex项目的排列顺序有影响(在不改变DOM结构,要实现反方向排版时,非常适合)。除了 flex-direction 可以影响Flex项目排列顺序之外,在Flex项目中显式使用 order 属性也可以,并且可以在不影响DOM结构,按照你自己任意想要的意图进行排序。

目前在imgcook中使用Flexbox布局时,在Flex容器上都会显式的设置 flex-direction 的值,即使是默认值: row 。在布局算法优化中,可以做相应的处理,只有在非 row 时才在Flex容器上显式设置 flex-direction :

指定主轴方向

控制Flex项目是否换行(Flex行)

使用 flex-wrap 可以控制Flex项目在Flex容器换行的方式:

控制Flex项目是否换行

只有所有Flex项目宽度总和大于Flex容器主轴尺寸时,设置 flex-wrap 属性才能生效。

flex-wrap 取值为非 nowrap (即 wrap  和 wrap-reverse )都可以让Flex项目换行(列)显式,其中 wrap-reverse 表现行为和 wrap 刚好相反。

组合效果 表现效果
效果1 控制Flex项目是否换行
效果2 控制Flex项目是否换行
效果3 控制Flex项目是否换行
效果4 控制Flex项目是否换行
效果5 控制Flex项目是否换行
效果6 控制Flex项目是否换行
效果7 控制Flex项目是否换行
效果8 控制Flex项目是否换行

flex-direction 和 flex-wrap 可以简写成 flex-flowflex-flow 使用时可以只显式设置一个值,也可以显式设置两个值:

  • flex-flow 只显式设置一个值,并且该值和 <flex-direction> 相匹配时, flex-wrap 会取值 initial 
  • flex-flow 只显式设置一个值,并且该值和 <flex-wrap> 相匹配时, flex-direction 会取值 initial 
  • flex-flow 显式设置两个值时, flex-direction 和 flow-wrap 没有先后顺序之分,即可 flex-flow: column wrap 和 flex-flow: wrap column 等同

主轴方向对齐方式

在Flex容器中使用 justify-content 来控制Flex项目在Flex容器主轴方向的对齐方式,也可以用来分配Flex容器中主轴方向的剩余空间。使用 justify-content 分配Flex容器剩余空间,主要是将剩余空间按不同的对齐方式,将剩余空间分配给Flex项目的两侧,即控制Flex项目与Flex项目之间的间距。

justify-content 存在两个规范中:

在Flexbox 布局模块中, justify-content 取值只要有以下六种:

主轴方向对齐方式

需要注意 space-between 、 space-around 和 space-evenly 三者的差异:

主轴方向对齐方式

  • space-between 会让第一个Flex项目的盒子起始边缘与Flex容器主轴起点相稳合,最后一个Flex项目的盒子结束边缘与Flex容器主轴终点相稳合,其它相邻Flex项目之间间距相等。当Flex容器中只有一个Flex项目时,其表现行为和 flex-start 等同
  • space-around 会让第一个Flex项目的盒子起始边缘与Flex容器主轴起点间距和最后一个Flex项目的盒子结束边缘与Flex容器主轴终点间距相等,并且等于其他相邻两个Flex项目之间间距的一半。当Flex容器中只有一个Flex项目时,其表现行为和 center 等同
  • space-evenly 会让第一个Flex项目的盒子起始边缘与Flex容器主轴起点间距和最后一个Flex项目的盒子结束边缘与Flex容器主轴终点间距相等,并且等于其他相邻两个Flex项目之间间距。当Flex容器中只有一个Flex项目时,其表现行为和 center 等同

如果Flex容器没有额外的剩余空间,或者说剩余空间为负值时, justify-content 的值表现形式:

  • flex-start 会让Flex项目在Flex容器主轴结束点处溢出
  • flex-end 会让Flex项目在Flex容器主轴起点处溢出
  • center 会让Flex项目在Flex容器两端溢出
  • space-between 和 flex-start 相同
  • space-around  和 center 相同
  • space-evenly 和 center 相同

在Flexbox布局中,可以使用这些属性很好控制Flex容器的剩余空间,比如:

主轴方向对齐方式

侧轴方向对齐方式

在Flexbox容器中使用 align-items 来控制Flex项目在侧轴方向的对齐方式。

侧轴方向对齐方式

align-items 的默认值是 stretch ,但只有Flex项目示显式设置 height (或 width )值,Flex项目才会被拉伸填满整个Flex容器。

如果Flex容器没有剩余空间或剩余空间为负值是:

  • flex-start 会让Flex项目在Flex容器侧轴终点处溢出
  • flex-end 会让Flex项目在Flex容器侧轴起点处溢出
  • center 会让Flex项目在Flex容器侧轴两侧溢出
  • baseline 会让Flex项目在Flex容器侧轴终点溢出,有点类似于 flex-start

 

多行(列)对齐方式

align-content 只适用于Flex容器在没有足够空间(所有Flex项目宽度之和大于Flex容器主轴尺寸),并且显式设置 flex-wrap 的值为非 wrap 时。

多行(列)对齐方式

align-content 表现行为有点类似于 justify-cotent 控制Flex项目在主轴方向的对齐方式(分配Flex容器主轴剩余空间),而 align-content 可以用来控制多行状态下,行在Flex容器侧轴的对齐方式(分配Flex容器侧轴剩余空间)。可以把 align-content 状态下侧轴中的整行当作是 justify-content 状态下单个Flex项目。

align-content 还有一点不同之处,多了一个 stretch 值。当Flex容器中所有行的尺寸之和大于Flex容器侧轴尺寸(Flex容器侧轴没有可用空间或可用空间为负值)时,各值表现行为:

  • flex-start 会让Flex容器的行在侧轴结束点溢出
  • flex-end 会让Flex容器的行在侧轴起点溢出
  • center 会让Flex容器行在侧轴两端溢出
  • stretch 表现行为类似于 flex-start 
  • space-around 表现行为类似于 center 
  • space-between 表现行为类似于 flex-start 
  • space-evenly 表现行为类似于 center

 

间距(行与行,列与列)

gap 用来控制Flex项目之间的间距,但会忽略Flex项目与Flex容器边缘的间距:

间距(行与行,列与列)

运用于Flex项目的属性

Flex项目自身对齐方式

在Flex容器上可以使用 justify-content 、 align-content 以及 align-items 分配Flex容器主轴和侧轴的空间(控制Flex容器中所有Flex项目对齐方式)。如果你需要对Flex项目个体对齐方式做处理,可以使用 align-self :

Flex项目自身对齐方式

align-self 取不同值的效果:

Flex项目自身对齐方式

Flex项目的 align-self 显式设置值为 auto 时不会覆盖Flex容器的 align-items ;另外如果在Flex项目上显式设置 margin 的值为 auto 时,Flex项目的 align-self 值将会失效

Flex项目自身对齐方式

类似上图这样的场景, align-self 就非常实用。

Flex项目排序

在Flex容器中使用 flex-direction 可以对Flex容器中的所有Flex项目按“ LTR ”、“ RTL ”、“ TTB ” 或 “ BTT ” 方向排列。

  • LTR : flex-driection: row 
  • RTL : flex-direction: row-reverse 
  • TTB : flex-direction: column 
  • BTT : flex-direction: column-reverse

 

在Flex项目上,还可以使用 order 指定具体的数值,在不改变DOM结构之下对Flex项目进行排序,其中数值越大,越在往后排:

Flex项目排序

在一些左右,上下互换顺序的时候,除了 flex-direction 之外,还可以在Flex项目设置 order :

Flex项目排序

Flex项目伸缩计算

Flex项目中使用 flex 属性可以根据Flex容器的可用空间对自身做伸缩计算,其包含三个子属性: flex-basis 、 flex-shrink 和 flex-grow 。这几个属性都有其初始值:

  • flex-grow 的初始值为 0 
  • flex-shrink 的初始值为 1 
  • flex-basis 的初始值为 auto

 

flex 的三个子属性: flex-grow (扩展比率)、 flex-shrink (收缩比率)和 flex-basis (伸缩基准)。这三个属性可以控制Flex项目,具体的表现如下:

  • flex-grow :设置Flex项目的扩展比率,让Flex项目得到(扩展)多少Flex容器剩余空间(Positive Free Space),即Flex项目可能会变大
  • flex-shrink :设置Flex项目收缩比率,让Flex项目减去Flex容器不足的空间(Negative Free Space),即Flex项目可能会变小
  • flex-basis :Flex项目未扩展或收缩之前,它的大小,即指定了Flex项目在主轴方向的初始大小

flex 属性可以指定 1个值(单值语法) 、 2个值(双值语法)  或 3个值(三值语法) 。

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

  • 一个无单位的数( <number> ),比如 flex: 1 ,这个时候它会被当作 <flex-grow> 的值
  • 一个有效的宽度( width )值,比如 flex: 30vw ,这个时候它会被当作 <flex-basis> 的值
  • 关键词 none 、 auto 或 initial (即初始值)

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

  • 一个无单位的数( <number> ),它会被当作 <flex-shrink> 的值
  • 一个有效的宽度( width )值,它会被当作 <flex-basis> 的值

三值语法:

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

flex 属性的取值可以是:

  • auto :Flex项目会根据自身的 width 和 height 来确定尺寸,但Flex项目根据Flex容器剩余空间进行伸缩。其相当于 flex: 1 1 auto 
  • initial :Flex项目会根据自身的 width 和 height 来设置尺寸。它会缩短自身以适应Flex容器,但不会伸长并吸收Flex容器中的额外剩余空间来适应Flex容器。其相当于 flex: 0 1 auto 
  • none :Flex项目会根据自身的 width 和 height 来设置尺寸。它是完全非弹性的(既不会缩短,也不会伸长来适应Flex容器)。其相当于 flex: 0 0 auto 
  • <flex-grow> :定义Flex项目的 flex-grow 属性,取值为 <number> 
  • <flex-shrink> :定义Flex项目的 flex-shrink 属性,取值为 <number> 
  • <flex-basis> :定义Flex项目的 flex-basis 属性。若值为 0 ,则必须加上单位,以免被视作伸缩性

flex-grow 计算

flex-grow 计算公式:

flex-grow计算公式

示例:

假设Flex容器中有四个Flex项目,具体参数:

  • Flex容器的宽度是 80vw 
  • Flex容器中共有四个Flex项目,并且每个Flex项目的宽度是 10vw 
  • Flex项目宽度总和为 10vw x 4 = 40vw 
  • Flex容器的剩余空间为 80vw - 40vw = 40vw 
  • Flex项目的 flex-grow 的值分别是 0 、 1 、 2 和 3 ,所有Flex项目的 flex-grow 总和为 0 + 1 + 2 + 3 = 6

 

flex-grow 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-grow 0 1 2 3 0 + 1 + 2+ 3 = 6
Flex项目宽度 10vw 10vw 10vw 10vw 10vw x 4 = 40vw
Flex容器宽度 80vw
Flex容器剩余空间 80vw - 40vw = 40vw
Flex项目新宽度 ? ? ? ?

计算过程:

flex-grow计算公式

计算出来的结果:

flex-grow 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-grow 0 1 2 3 0 + 1 + 2+ 3 = 6
Flex项目宽度 10vw 10vw 10vw 10vw 10vw x 4 = 40vw
Flex容器宽度 80vw
Flex容器剩余空间 80vw - 40vw = 40vw
Flex项目新宽度 10vw 16.667vw 23.333vw 30vw

flex-grow 的取值还可以是 小数值 。如果将上面示例中的 flex-grow 的值分别换成 0 、 0.1 、 0.2 和 0.3 ,这个时候 flex-grow 的总和(所有Flex项目的 flex-grow 和)就是 0.6  ,该值小于 1 。这个时候,Flex项目同样会根据 flex-grow 增长因子来瓜分Flex容器的剩余空间,Flex自身宽度也会变大,但Flex容器的剩余空间不会被全部瓜分完,因为所有 flex-grow 和小于 1 。就该示例下,只瓜分了 Flex容器剩余空间宽度的 60% 。

如果 flex-grow 和小于 1 ,其计算公式如下:

flex-grow计算公式

flex-grow 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-grow 0 .1 .2 .3 0 + 1 + 2+ 3 = .6
Flex项目宽度 10vw 10vw 10vw 10vw 10vw x 4 = 40vw
Flex容器宽度 80vw
Flex容器剩余空间 80vw - 40vw = 40vw
Flex项目新宽度 10vw 14vw 18vw 22vw

即使Flex容器中所有Flex项目的 flex-grow 和大于 1 ,但也不可以绝对地说,Flex项目可以根据自身的 flex-grow 所占比率来瓜分Flex容器的剩余空间。因为元素的尺寸会受 max-width 的影响。当Flex项目显式设置了 max-width的值时,当Flex项目根据flex-grow计算出来的宽度大于 max-width时,Flex项目会按 max-width的值为准。比如我们在前面的示例上,给所有Flex项目设置一个 max-width 的值为 18vw ,此时计算过程和结果如下:

flex-grow 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-grow 0 1 2 3 0 + 1 + 2+ 3 = 6
Flex项目宽度 10vw 10vw 10vw 10vw 10vw x 4 = 40vw
Flex容器宽度 80vw
Flex容器剩余空间 80vw - 40vw = 40vw
Flex项目计算出的新宽度 10vw 16.667vw 23.333vw 30vw
Flex项目设置最大宽度 18vw 18vw 18vw 18vw
Flex项目最终宽度 10vw 16.667vw 18vw 18vw

这个时候Flex容器剩余空间并没有全部用完, 40vw - 0vw - 6.667vw - 8vw - 8vw = 17.333vw ,即Flex容器还有 17.333vw 的剩余空间。

如果Flex项目没有显式设置与宽度有关的属性(包括 flex-basis ),那么 flex-grow 在计算时,Flex项目会按其内容的宽度来计算。

flex-grow计算公式

从上图可以得到:

  • Flex容器的宽度是 804px 
  • Flex项目的宽度分别是 43.36px 、 92.09px 、 140.83px 和 189.56px ,所有Flex项目宽度的总和为 465.84px 
  • Flex容器的剩余宽度为 804px - 465.84px = 338.16px 
  • 所有Flex项目的 flex-grow 值为 1 ,即 所有Flex项目的 flex-grow 总和为 4

 

将相应的值套用到 flex-grow 的公式中,可以得到:

flex-grow 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-grow 1 1 1 1 1 x 4 = 4
Flex项目宽度 43.36px 92.09px 148.83px 189.56px 465.84px
Flex容器宽度 804px
Flex容器剩余空间 338.16px
Flex项目新宽度 127.9px 176.63px 225.37px 274.1px

注意,不同的浏览器对小数处理有差异。

flex-shrink 计算

flex-shrink 计算公式:

flex-shrink计算公式

示例:

假设Flex容器有四个Flex项目,具体参数如下:

  • Flex容器的宽度是 40vw 
  • Flex容器中共有四个Flex项目,并且每个Flex项目的宽度都是 15vw 
  • Flex项目宽度总和为 15vw x 4 = 60vw 
  • Flex容器的不足空间为 40vw - 60vw = -20vw 
  • Flex项目的 flex-shrink 的值分别是 0 、 1 、 2 和 3 ,所有Flex项目的 flex-shrink 总和为 0 + 1 + 2 + 3 = 6

 

flex-shrink 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-shrink 0 1 2 3 0 + 1 + 2+ 3 = 6
Flex项目宽度 15vw 15vw 15vw 15vw 15vw x 4 = 60vw
Flex容器宽度 40vw
Flex容器不足空间 60vw - 40vw = 20vw
Flex项目新宽度

计算过程:

flex-shrink计算公式

计算出来的结果:

flex-shrink 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-shrink 0 1 2 3 0 + 1 + 2+ 3 = 6
Flex项目宽度 15vw 15vw 15vw 15vw 15vw x 4 = 60vw
Flex容器宽度 40vw
Flex容器不足空间 60vw - 40vw = 20vw
Flex项目收缩比例 0 0.1667 0.333 0.5
Flex项目新宽度 15vw 11.67vw 8.33vw 5vw

flex-shrink 的计算还可以甚至另一个公式来计算:

flex-shrink计算公式

flex-shrink 和 flex-grow 类似,也可以取小数值。如果Flex容器中所有Flex项目的 flex-shrink 总和小于 1 ,那么Flex容器的不足空间就不会被Flex项目按收缩因子瓜分完,Flex项目会依旧会溢出Flex容器。

flex-shrink 总和小于 1 时,其计算公式如下:

flex-shrink计算公式

基于上面的示例,把Flex项目的 flex-shrink 分别设置为 0 、 0.1 、 0.2 和 0.3 ,计算过程如下:

flex-shrink 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-shrink 0 0.1 0.2 0.3 0.6
Flex项目宽度 15vw 15vw 15vw 15vw 15vw x 4 = 60vw
Flex容器宽度 40vw
Flex容器不足空间 60vw - 40vw = 20vw
Flex项目新宽度 15vw 13vw 11vw 9vw

即使Flex容器中所有Flex项目的 flex-shrink 和大于 1 ,但也不可以绝对地说,Flex项目可以根据自身的 flex-shrink 所占比率来瓜分Flex容器的不足空间。因为元素的尺寸会受 min-width 的影响。当Flex项目显式设置了 min-width的值时,当Flex项目根据 flex-shrink计算出来的宽度小于 min-width时,Flex项目会按 min-width的值为准。比如我们在前面的示例上,给所有Flex项目设置一个 min-width 的值为 10vw ,此时计算过程和结果如下:

flex-shrink 公式中变量名称 Flex1 Flex2 Flex3 Flex4 总数
Flex项目的 flex-shrink 0 1 2 3 0 + 1 + 2+ 3 = 6
Flex项目宽度 15vw 15vw 15vw 15vw 15vw x 4 = 60vw
Flex容器宽度 40vw
Flex容器不足空间 60vw - 40vw = 20vw
Flex项目收缩比例 0 0.1667 0.333 0.5
Flex项目设置最小宽度 10vw 10vw 10vw 10vw
Flex项目计算出的新宽度 15vw 11.67vw 8.33vw 5vw
Flex项目最终宽度 15vw 11.67vw 10vw 10vw

在这个情况之下,Flex项目的最终宽度总和还是会大于Flex容器宽度,Flex项目同样会溢出Flex容器。

flex-shrink 和 flex-grow 还有一点相似,那就是未显式给Flex容器的Flex项目显式设置与宽度有关的属性时,那么Flex项目的初始宽度会以其内容的宽度作为基准计算值。

flex-shrink 有一点和 flex-grow 完全不同,如果某个Flex项目按照 flex-shrink 计算出来的新宽度趋向于 0 时,Flex项目将会按照该元素的 min-content 的大小来设置宽度,同时这个宽度将会转嫁到其他Flex项目,再按相应的收缩因子进行收缩。

比如我们将第四个Flex项目的 flex-shrink 的值从 3 改为 9 。根据上面提供的公式,可以获知,Flex项目4的新宽度等于 15vw - (20vw ÷ 12) × 9 = 0 

计算出来的宽度为 0 ,但实际上这个时候渲染出来的宽度是该项目的 min-content (该示例就是“shrink”单词的宽度,如下图所示),大约 47.95px (约 3.66vw )。那么这个值将会分成 3 份(因为该例另外三个Flex项目的 flex-shrink 是 0 、 1 和 2 ),并且对应的Flex项目会继续分配本应Flex项目4要收缩的宽度。即:

  • Flex项目1新宽度等于 15vw - 20 ÷ 12 × 0 - 3.66 ÷ 3 × 0 = 15vw  (约 196.5px )
  • Flex项目2新宽度等于 15vw - 20 ÷ 12 × 1 - 3.66 ÷ 3 × 1 = 12.113vw  (约 158.6847px )
  • Flex项目3新宽度等于 15vw - 20 ÷ 12 × 2 - 3.66 ÷ 3 × 2 = 9.227vw  (约 120.869px )

浏览器视窗宽度在 1310px 状态下渲染出来的结果如下:

flex-shrink计算公式

在Flexbox布局模块中,基于前面提到的Flex容器的对齐属性、Flex项目中的 flex-shrink 和 flex-grow 我们就可以很好的处理Flex容器的剩余空间和不足空间:

  • Flex容器有剩余空间(所有Flex项目的宽度总和小于Flex容器的宽度),如果设置了 flex-grow ,Flex项目会根据扩展因子分配Flex容器剩余空间;在未设置 flex-grow 时,在Flex容器中是否设置了对齐方式,如果是,那么会按对齐方式分配Flex容器剩余空间,如果不是,Flex容器剩余空间不变
  • Flex容器有不足空间(所有Flex项目的宽度总和大于Flex容器的宽度),如果设置了 flex-shrink 值为 0 ,Flex项目不会收缩,Flex项目溢出Flex容器;如果未显式设置 flex-shrink 值,Flex项目分平均分配Flex容器不足空间,Flex项目会变窄(Flex项目的 flex-shrink 的默认值为 1 ),如果显式设置了 flex-shrink 的值为非 0 的不同值,那么Flex项目会按照不同的收缩因子分配Flex容器不足空间,Flex项目同样会变窄

具体的我们可以绘制一张这方面的流程图:

flex-shrink计算公式

flex-basis 计算

flex-basis 的计算相对于 flex-grow 和 flex-shrink 更略为复杂,因为它和Flex项目的 内容(Content) 、** width **、 min-width max-width 都有关系。这里的关系指的就是它们之间的权重关系,简单地说,在Flex项目中同时出现这几个属性时,最终由谁来决定Flex项目的宽度

在Flexbox布局中,可以使用 flex-basis 来实始化Flex项目尺寸,即 在任何Flex容器空间(剩余空间或不足空间)分配发生之前初始化Flex项目尺寸

事实上,在Flexbox布局模块中 设置Flex项目的尺寸大小存在一个隐式的公式:

content  ➜ width  ➜ flex-basis

简单地说,如果Flex项目未显式指定 flex-basis 的值,那么 flex-basis 将回退到 width (或 inline-size )属性;如果未显式指定 width (或 inline-size )属性的值,那么 flex-basis 将回退到基于Flex项目内容计算宽度。不过,决定Flex项目尺寸大小,还受 flex-grow 和 flex-shrink 以及Flex容器大小的影响。而且Flex项目 最终尺寸 会受 min-widthmax-width(或 min-inline-size 、 max-inline-size )属性限制。这一点必须得注意。

来看一个示例:

<div class="flex__container">
    <div class="flex__item"></div>
    <div class="flex__item"></div>
    <div class="flex__item"></div>
    <div class="flex__item"></div>
</div>

.flex__container {
    width: 600px;
    display: flex;

    border: 1px dashed #f36;
    align-items: stretch;
}

Flex项目不显式的设置任何与尺寸大小有关系属性,即用 content来撑开Flex项目

<div class="flex__container">
    <div class="flex__item">Lorem ipsum dolor sit amet</div>
    <div class="flex__item">Lorem ipsum dolor sit amet consectetur adipisicing elit</div>
    <div class="flex__item">Fugiat dolor nihil saepe. Nobis nihil minus similique hic quas mollitia.</div>
    <div class="flex__item">Lorem ipsum dolor sit amet consectetur adipisicing elit. Molestias consequuntur sequi suscipit iure fuga ea!</div>
</div>

在这个示例中,并没有显式给Flex项目设置 flex-basis 属性,此时 flex-basis 会取默认值 auto :

flex-basis

显式给Flex项目设置 width 值。

:root { 
    --width: 120px; 
} 

.flex__item { 
    width: var(--width); 
}

这个时候所有Flex项目宽度都是相等的:

flex-basis

浏览器计算出来的 flex-basis 值依旧为 auto ,但显式的设置了 width: 120px ,最终 width 属性的值决定了Flex项目的尺寸大小。

显式给Flex项目设置 flex-basis 值,即Flex项目同时有 width 和 flex-basis 值

:root {
    --width: 120px;
    --flexBasis: 150px;
}

.flex__container {
    width: 800px;
}

.flex__item {
    width: var(--width);
    flex-basis: var(--flexBasis);
}

虽然在Flex项目同时显式设置了 width 和 flex-basis ,但Flex项目最终的尺寸大小采用了 flex-basis 的值:

flex-basis

在Flexbox布局模块中影响Flex项目尺寸大小应该根据其隐式公式(即 content  ➜ width  ➜ flex-basis  )来进行判断。如果要显式给Flex项目设置尺寸大小,其最佳方式是 使用 flex-basis ,而不是 width (或 inline-size )。

最后还有一点千万别忘记:

使用 flex-basis 时会受min-widthmax-width(或逻辑属性中min-inline-sizemax-inline-size )的限制

在CSS中,如果元素同时出现 width 、 min-width 和 max-width 属性时,其权重计算遵循以下规则:

  • 元素的 width 大于 max-width 时,元素的 width 等于 max-width ,即 max-width 能覆盖 width ( max-width 胜出
  • 元素的 width 小于 min-width 时,元素的 width 等于 min-width ,即 min-width 能覆盖 width ( min-width 胜出
  • min-width 大于 max-width 时, min-width 优先级将高于 max-width ( min-width 胜出

如果Flex项目同时出现 width 、 flex-basis 和 min-width 时,具体的运算过程如下:

  • 根据法则: content  ➜ width  ➜ flex-basis ,判断出运用于Flex项目的值,即 flex-basis 会运用于Flex项目 ( flex-basis 胜出)
  • 再根据法则:Flex项目的 width  小于 min-width 时,Flex项目的 width  等于 min-width ,即 min-width 能覆盖 width (min-width 胜出

这样一来,如果 flex-basis 小于 min-width 时,Flex项目的宽度会取值 min-width ,即 min-width 覆盖 flex-basis (min-width胜出)

如果Flex项目同时出现 width 、 flex-basis 和 max-width 时,具体的运算过程如下:

  • 根据法则: content  ➜ width  ➜ flex-basis ,判断出运用于Flex项目的值,即 flex-basis 会运用于Flex项目( flex-basis 胜出)
  • 再根据法则:Flex项目的 width  大于 max-width 时,Flex项目的 width  等于 max-width ,即 max-width 能覆盖 width ( max-width 胜出

这样一来,如果 flex-basis 大于 max-width 时,Flex项目的宽度会取值 max-width ,即 max-width 覆盖 flex-basis ( max-width 胜出)

如果Flex项目同时出现 width 、 flex-basis 、 min-width 和 max-width 时,会在上面的规则上增加新的一条规则来进行判断:

min-width  大于 max-width  时, min-width 优先级将高于 max-width ( min-width胜出)

那么套用到Flex项目中:

  • flex-basis 大于 max-width ,Flex项目的宽度等于 max-width ,即 max-width 能覆盖 flex-basis ( max-width 胜出)
  • flex-basis 小于 min-width 时,Flex项目的宽度会取值 min-width ,即 min-width 覆盖 flex-basis ( min-width 胜出

由于 min-width 大于 max-width 时会取 min-width ,有了这个先取条件我们就可以将 flex-basis 和 min-width 做权重比较,即:** flex-basis 会取 min-width 。反过来,如果 min-width 小于 max-width 时则依旧会取 max-width ,同时要是 flex-basis 大于 max-width 就会取 max-width **。

如果你理解了的话,可以使用更简单的规则来决定用于Flex项目的尺寸。

首先根据 content  ➜ width  ➜ flex-basis 来决定用哪个来决定用于Flex项目。如果Flex项目显式设置了 flex-basis 属性,则会忽略 content 和 width 。而且 min-width 是用来设置Flex项目的下限值; max-width 是用来设置Flex项目的上限值。

用一个简单的流程图来描述:

flex-basis

注,Flex项目上的 flex-shrink 和 flex-grow 也会影响Flex项目尺寸大小

如果你想更深入的了解Flexbox中Flex项目的计算,建议你花点时间阅读:

Flex项目上的 margin

在Flex项目显式设置 margin 的值为 auto 可以灵活的控制单个Flex项目在Flex容器中的位置:

flex项目上的margin

比如像下图这样的效果,使用 margin-left: auto 就非常的实用:

flex项目上的margin

案例整理

padding 与自动宽问题

padding 与自动宽问题

针对这个案例,较好的方案对于内部元素不显式设置任何关于 padding 和 margin 的属性。人工实现可能会像下面这样:

<div class="flex__container">
    <span class="coupon">卷</span>
    <span class="divider"></span>
    <span class="price">&yen;1000</span>
</div>

.flex__container {
    display: inline-flex;
    min-width: 200px;
    height: 60px;
    
    border: 1px solid rgba(255, 0, 54, 1);
    background-color: rgba(255, 0, 54, 0.1);
    border-radius: 4px;
    color: #ff0036;
    font-size: 24px;
    font-weight: 400;
}

.flex__container > span {
    display: inline-flex;
    justify-content: center;
    align-items: center;
}

.divider {
    border-right: 1px dashed currentColor;
}

.coupon {
    min-width: 50px;
}

.price {
    flex: 1;
    min-width: 0;
    padding: 0 10px;
}

padding 与自动宽问题

  • ① 像类似Button,Badge等(外形看上去类似于内联块),设置Flex容器为 inline-flex ,并且给其设置一个 min-width (默认情况下等同于Sketch设计稿)和 一个 height 
  • ② 从设计稿上分析可以得到,前面“卷”这个宽度是可知的,在该元素上设置一个固定宽度 width 
  • ③ 一个约 1px 的分割线,可以使用 border 或者定死宽度 width 
  • ④ 最右侧“价格”是下不可定因素,在Flex项目上,可以将其显式设置 flex: 1 ,让该部分占用Flex容器的剩余空间
  • ⑤ 为了让“价格”更具有扩展性,当其数值扩展到Flex容器无剩余空间时,数字会紧挨Flex容器主轴终点和分割线,为了让视觉上更友好,要以在“价格”容器设置一个 padding-left 和 padding-right

 

小结

这篇笔记涉及到了Flexbox规范中的大部分内容以及一些临界点,在使用Flexbox来完成UI上的布局除了文章中提到的一些基础内容和细节之外,还有一些其他的东西。比如Flex容器中的定位,层级计算等,Flex容器和Flex项目碰到overflow以及Flex容器中的滚动计算等。这些对于场景具有较强的指定性,对于边界的处理也过于复杂。在我们平常使用Flexbox很少甚至不怎么会碰到。因此没有在文章中罗列。

如果你在使用Flexbox,特别是在使用imgcook自动还原UI,效果和你预期不一样,或者有不合理的地方,都可以随时来撩偶。