起因
对于Flex布局,阅读了 大漠老师和其他老师写的文章后,我还是不太理解Flexbox是如何弹性的计算子级项目的大小以及一些其他细节。在大漠老师的帮助下,我去查阅Flexbox 的 W3C 规范文档。
注:本篇博文不适合未接触过Flex 布局的人,如果想了解flex 布局基础。请参考理解Flexbox:你需要知道的一切
对于flex盒模型的设计期望
flex盒模型是被期望设计成:
- 在任何流动的方向上(包括上下左右)都能进行良好的布局
- 可以以逆序 或者 以任意顺序排列布局
- 可以线性的沿着主轴一字排开 或者 沿着侧轴换行排列
- 可以弹性的在任意的容器中伸缩大小(今天重点研究的主题)
- 可以使子元素们在容器主轴方向上 或者 在容器侧轴方向上 进行对齐
- 可以动态的 沿着主轴方向 伸缩子级的尺寸,与此同时保证父级侧轴方向上的尺寸
主轴和侧轴
很有必要先向大家解释清楚 3个问题
首先每一根轴都包括 三个东西:维度、方向、尺寸
什么意思呢?
- 所谓的维度实际上就是意思就是子元素 横着排还是竖着排(
x
轴 或 y
轴)
- 方向 即排列子元素的顺序 顺序还是逆序
- 尺寸 即
width
[height
] : 每一个子元素在主轴方向所占的位置的总和 如果主轴是水平的,那么尺寸就是父元素内所有item
的outerWidth
总和,如果主轴是垂直的,那么尺寸就是父元素的outerHeight
主轴是依靠 flex-direction
和 所有子元素在主轴方向上的item-size
的总和确定的,flex-direction
这个属性可以控制子元素的排列方向和排列顺序。
侧轴是依靠 flex-wrap
和 所有子元素在主轴方向上的item-size
的总和确定的,flex-wrap
可以控制子元素 在侧轴方向上的排列方式以及顺序。
而关于不同种类不同情况下的 item-size
我们会在下面讨论,现在您可以简单将它理解为width
[height
]。

盗规范中的一张图
为了方便 flex-direction + flex-wrap
合并成了一个属性 flex-flow
通过这个简单而复杂的属性,我们就能够控制所有子元素的水平和垂直方向,逆序排列和顺序,换行和不换行。
主侧轴的切换十分简单,当主轴设定的时候,它的垂直面,就默认被设定成了侧轴。如:
flex-flow: row-reverse wrap-reverse;
这条CSS属性能够告诉我们那些信息?
- 子元素是横着排列的,主轴是水平的横轴,侧轴是竖直的纵轴
- 子元素是逆序并沿着主轴排列的,从右到左
- 子元素是换行的
- 子元素是逆序并沿着侧轴排列的,从下到上
FFC (flex formatting context)
Flexbox 布局新定义了格式化上下文,类似 BFC(block formatting context)。有多类似呢? 就是除了布局和一些细节不同以外的一切规则都和 BFC 是相同的
。
注意?: 我所指的Flexbox 是指设置了?display: flex;
?或?display: inline-flex;
的盒子。不是指单单设置了?display: flex;
?的盒子。
例如,设置了?display: flex;
?或?display: inline-flex
的元素,和BFC一样,不会被浮动的元素遮盖,不会垂直外边距坍塌等等。
而对于设置了?display: inline-flex
?的盒子来说,我们可以类比?display: inline-box;
行理解。即 一个被行列化后的 Flexbox。它不会独占一行,但是可以设置宽和高。
与BFC的细微区别
但需要注意的是以下几点细节,Flexbox 布局 和 Block 布局是有细微区别的
- Flexbox 不支持?
::first-line
?和?::first-letter
?这两种伪元素
-
vertical-align
?对 Flexbox 中的子元素 是没有效果的
-
float
?和?clear
?属性对 Flexbox 中的子元素是没有效果的,也不会使子元素脱离文档流(但是对Flexbox 是有效果的!)
- 多栏布局
(column-*)
?在 Flexbox 中也是失效的,就是说我们不能使用多栏布局在Flexbox 排列其下的子元素(鱼和熊掌不可得兼嘛)
- Flexbox 下的子元素不会继承父级容器的宽
flex item(flex 子元素)
CSS解析器会把 定义了?display: flex;
?和?display: inline-flex;
?的 Flexbox 下的子元素外部装进一个看不见的盒子里,我们通过排列这些盒子来达到排序、布局、 伸缩的目的。
规范中把这种盒子 称为?flex item
,而子元素中包括了 标签节点 以及 文本节点。标签节点很容易理解,需要注意的是文本节点。
默认情况下,flex
?会将?连续的文本节点?装进 flex-item 之中,使文本可以和标签节点一起排序和定位。
值得注意的是,空格也是文本节点,所以?white-space
?会影响Flexbox 中的布局:

设置了white-space: pre
?的Flexbox
flex-item-size 如何计算的
item-size
(尺寸)为主轴方向上item
的?content
?再加上自身的margin
?、?border
?和?padding
?就是这个?item
?的尺寸。
在规范中 介绍了 flex-item content 的计算方式
分为以下这几种情况
1. flex-basis 的优先级比 width[height]: 非auto; 高
如果子元素没有内容和默认固定宽高,且设置了flex-basis
。flex-item content
以flex-basis
来决定,无论width[height]
?设置了多少。
(可理解为?flex-basis
?比?width[height]: 非auto;
的优先级高)

flex-basis
的优先级比width[height]
高,无论width[height]
设置多少,flex-item content都以flex-basis
来决定。
2.元素存在默认宽高
如果子元素有默认固定宽高(例如input
?标签)、并且设置了?flex-basis
,那么它的?content
以 固定宽高为下限,如果flex-basis
?超过了固定宽高,那么flex-basis
则成为其?content
,如果flex-basis
比固定宽高小,那么以固定宽高为?content
。

对于固定元素的尺寸设定
3.元素存在 min-width[height] 或者 max-width[height]
如果flex-item
?有min-width[min-height]
?的限制,那么flex-item content
按照?min-width
?值为下限,如果?flex-basis
?的值大于?min-width[min-height]
?那么flex-item content
以?flex-basis
?计算。
如果flex-basis
?的值小于?min-width[min-height]
?那么flex-item content
以min-width[min-height]
?计算:

如果?min-width[min-height]
?的值已经超出了容器的尺寸,那么即使设置了?flex-shrink
。 CSS解析器也不会进行将这个item
的 content shrink,而是坚持保留它的min-width[min-height]
:

如果Flexbox 设置的min-width
?超出了flex container 的范围,不会对其进行压缩。
反之,如果设置了?max-width[height]
?的值,那么设置flex-basis
?无法超过这个值,对于flex-grow
?也仅仅只会增长到?max-width[height]
?这个上限。
在下面的章节,我们会仔细讨论这种情况下,布局的计算。
4.width[height]: auto; 优先级等于 flex-basis。
前面提到,如果给item同时设置了width[height]
?和?flex-basis
的话。flex-item content以flex-basis
来决定。但是实际上默认的?width[height]: auto;
?优先级是等于?flex-basis
的。
CSS解析器对比两者的值,两者谁大取谁 作为item的基本尺寸,如果一个item没有内容,flex-item content就会以flex-basis
来决定。
但是如果item有了内容,且内容撑开的尺寸比flex-basis
大,那么flex-item content就会以width[height]: auto;
?来决定,且无法被 shrink。反之,如果比flex-basis
小,flex-item content就会以flex-basis
来决定:

width: auto;
?内容长度比?flex-basis
?大,则 flex-item content以内容长度来决定,且无法shrink

如果?flex-basis
?的长度大于文字内容长度,那么flex-item content以?flex-basis
?来决定

同时设置了flex-basis: 800px;
?和?width: 1px;
?flex-item content以?flex-basis
?来决定,可以发生shrink

注意2号盒子我设置了?flex-shrink: 1;
?1号盒子和3号盒子我设置了?flex-shrink: 0;
?意思就是我将所有的需要shrink的空间都压到了2号盒子上,总共的需要 shrink的空间为?0 * 600 + 1 * 20 + 0 * 100 = -20
;而2号盒子只有20
的空间,理应被完全shrink变为0
,但是值得注意的是2号盒子并没有被完全 shrink,还保留了一个文字的距离。
除此之外,overflow: hidden;
?也会影响

overflow: hidden;
?把文字长度限制在了600px
; 小于?flex-basis: 700px;
所以flex-item content以flex-basis
来决定,可以 shrink。
隐藏属性对 items-size 的影响
我针对了?display: none; visibility: hidden; visibility: collapse; transform: scale;
?等属性对 items 进行测试。
结果如下:
- 如果设置了?
visibility: hidden; | visibility: collapse; | transform: scale;
的flex-item content 依然被算进主轴尺寸,CSS 解析器依然会以他们?flex-grow | flex-shrink
?将可用空间 或者 负可用空间 分配给他们
- 如果设置了
display: none;
?CSS解析器不会对该item的空间进行计算,也不会对其grow空间
关于position: absolute 对item影响
position: absolute
?也是适用 Flexbox 中的子元素的,并且,设置了position: absolute
属性的子元素,也会受到 Flexbox 排列的影响。

设置了absolute
?的子元素重叠在了一起,但是依然会受到?align-items: center;
?的影响而居中。对于 Flexbox 来说,设置了position: absolute;
并不会对其下的子元素产生任何影响。
我们重点看 Flexbox 下的子元素设置了absolute
?后有什么结果。
根据我做的实验,我得到了如下的结论:
flexbox 下设置了absolute的子元素的位置受3个方面的影响:
- flexbox 流下面的?
justify-content
?和?align-items
- 单个子元素的?
top
、left
、right
、bottom
- 单个子元素的?
margin
这里我们不讨论?translate
?因为?translate
?只是视觉上位置的改变:

设置了absolute
?的item 不会影响布局,如图,其中1 2 3 4 5 号是设置了absolute
的item,而 6 7 8 9号是没有设置absolute
的item Flexbox 我设置了?justify-content: center;
?和?align-items: center;
?每一个item我都给了?margin: 20px;
:
- 我们可以看到,由于
absolute
?的原因, 12345号的item 不会影响 6789号的排布。结论:脱离了文档流的 item 不会影响 正常的flex 布局。
- 如图上 4号 item,设置了
absolute
?但是没有设置?top / left
?这些值,位置居中偏下。结论:如果对子元素设置了?position: absolute;
?属性而没有设置 top?left
?这些值。子元素受 Flexbox 的justify-content: center
?、?align-items: center
?和?margin
?的影响
- 如图上1235 号item,我给他们分别设置?
top
、left
、right
、bottom
?等值。5号元素设置了margin-left: 50px;
?和?padding-bottom: -999999px;
结论:top
、left
、right
、bottom
?等值会覆盖?justify-content: center;
?和?align-items: center;
?设置的位置,使item 自由定位。margin
自始至终都会影响item的位置,而padding
不会(我试过padding
?设500px
?的情况,padding
?会影响item的大小)
- 如果对上图 12345号item 不设置?
top
、left
、right
、bottom
?等值。对父级的?justify-content
?和?align-items
?设置center
以外的其他值的话:如果设置了?flex-start
?所有元素不分开,定位在 主轴起点;如果设置了?flex-start
?所有元素不分开,定位在 主轴终点;如果设置justify-content: space-around;
效果等同于center
,即所有的元素叠在一起居中,且items不会产生间隔;如果设置了?justify-content: space-between;
?效果等同于?flex-start
,且items不会分开;如果设置了?align-items: flex-start;
?所有元素不分开,定位在 侧轴起点;如果设置了?align-items: flex-end;
?所有元素不分开,定位在 侧轴终点;如果设置了?align-items: stretch | baseline;
也是没有任何效果,items 不会跟随侧轴拉伸 或是 根据baseline 对齐
- 如果对单个item 设置 align-self,除了 flex-start | flex-end | center 有效之外,其他都失效
通过上面一系列的测试我们可以清楚的认识到?justify-content
、align-items
?和?top
、left
、right
、bottom
?都是位置属性,而且?top
、left
、right
、bottom
?会覆盖justify-content
和align-items
的值。(以上前提是一定要设置position: absolute
?不然?top
、left
、right
、bottom
?无效)。
而?margin
?的优先级是和?top
、left
、right
、bottom
?一样的,也就是说?margin
?和?top
、left
、right
、bottom
所设置的值会同时生效。
优先级排序为:?margin = justify-content | align-items > top、left、right、bottom
。
flex-basis、flex-grow、flex-shrink 以及相应的计算
flex-basis
、flex-grow
、flex-shrink
是FFC下特有的属性,只有父级元素设置了?display: flex | inline-flex;
?才会生效,并且只针对主轴方向生效。
如果 主轴是水平的,即?flex-direction: row;
?那么?flex-basis
、flex-grow
、flex-shrink
?控制的就是单个item的宽度。
如果 主轴是垂直的,即?flex-direction: column;
?那么?flex-basis
、flex-grow
、flex-shrink
?控制的就是单个item的高度。而flex-grow
?和?flex-shrink
?是用于 主轴方向上对 (负)可用空间 进行伸缩的。
这要分两种情况,换行或者不换行。
1.如果 flex-wrap: nowrap; 即不换行。
那么所有items 都会在主轴方向上的一条线上排列,CSS解析器会计算 items 在主轴方向上所占的空间 相对于 Flexbox 在主轴方向的所占的空间进行比较计算。
如果 items 所占的空间是小于Flexbox的 那么说明Flexbox 还没有填满,CSS解析器就会计算还有多少空间没有填满,根据每一个item所设置的flex-grow
?设置的值,将这些空间分配按比例分配给每一个item。

可用空间
如果 items 所占的空间是大于Flexbox的 那么说明Flexbox 被填满了,CSS解析器就会计算超出了多少空间,根据每一个item所设置的flex-shrink
设置的值,将这些空间分配按比例缩小每一个item

超出的空间
那么CSS解析器在这种情况下是怎样计算的呢?上一章我们劳神费力理解的item-size终于派上用场了。flex-grow
计算流程是:
可用空间 = 将flexbox-content - 每个item-size的总和
将每一个item所设置的 grow 全部加起来,将可用空间除以grow,得到单位分配空间。
根据每一个item 设置的 grow 来算,如果一个item 的grow 为 2,那么 这个 item 在主轴上的尺寸就需要延伸 2*单位分配空间的大小。
那么 每一个 item 就需要在原基础上 加上被分配的大小 就完成了grow:

分配前

分配后
简单理解就是,将超出的部分,可能多,也可能少,根据 grow 来分配成?x
?份,在根据每个 item 所设置的份数,将相差的部分分割给每一个item。
注意:flex-shrink
?的计算流程和flex-grow
的计算流程不同。
flex-shrink
计算流程是:先将所有项目 按照?flex-shrink * item-size
?的方式加起来 得到一个加权和,然后计算出 每一份 item 的 shrink比例:
shrink比例 = flex-shrink * item-size / 之前的总和;
然后计算 子元素超出父级的部分(负可用空间),每一个item 减去这个?shrink比例 * 负可用空间即可
:

shrink前

shrink后
2. 如果flex-wrap: wrap[wrap-reverse]; 即换行
那么items 都会先在主轴方向上的多条线上排列,CSS解析器先会计算 每一条线 在主轴方向上尺寸 相对于 Flexbox 容器的width[height]
进行比较计算,每条线之间互不干扰:

未分配之前

平均分配后
由于在一行内 如果item-size 累加超过了Flexbox 的尺寸就会另起一行进行排列,所以在这种情况下,不会存在 shrink 的情况,而只有 grow 的情况。
max-width[height] 情况下 flex-grow 的计算流程
由于可能存在某一个或多个item 设置了有max-width[height]
。所以,CSS引擎会先进行一次分配,分配后,统计那些有max-width[height]
的items,分配后是否有超出的剩余空间,然后对这些剩余空间再分配给那些没有设置max-width[height]
的item

再分配流程
min-width[height] 情况下 flex-shrink 的计算流程
由于可能存在某一个或多个item 设置了有min-width[height]
。所以,CSS引擎会先进行一次 shrink, shrink后,统计那些有min-width[height]
的items,shrink后是否有的剩余的未 shrink空间,然后对这些剩余空间再分配给那些没有设置min-width[height]
的item。
注意:第一次 shrink的算法和第二次分配未 shrink剩余空间的算法不同!

总结
Flexbox 布局很棒。免去了我们大量关于适配方面的工作,但是深入理解,并用好它还是需要一点门槛的。 再次感谢 @大漠老师 的鼎力帮助,谢谢。