深入理解 Flexbox
前言
过去,我们总是不得不忍受 float、display:table 这些布局方式带来的痛苦,不过现在是时候去拥抱一个更简洁的制作智能布局的现代语法 Flexbox
Flexbox 是什么
根据规范中的描述可知道,Flexbox 模块提供了一个有效的布局方式,即使不知道视窗大小或者未知元素情况之下都可以智能的,灵活的调整和分配元素和空间两者之间的关系
如何开始使用 Flexbox
幸运的是,入门超级简单
你要做的第一件事就是声明一个 Flex 容器
就像这样儿,声明了 Flex 容器之后,一个 Flexbox 格式化上下文就立即启动了
1 | <ul class="oul"> |
正常情况下 div 在 CSS 中垂直堆栈的,也就是说从上到下排列显示
图一
声明 Flex容器
1 | .oul { |
现在已经是一个 Flexbox 格式化上下文
图二
很简单 一行代码就能看到布局改变了子元素就像你使用了 float 一样是水平排列的
拿这个例子来说此时 ul 自动变成了 Flex,而 li 变成了 Flex 项目
记住这些名词,它们是 Flexbox 模块的基础
Flex 容器属性
flex-direction || flex-wrap || flex-flow || justify-content || align-items || align-content
解释这些属性之前,先来看一张 flex 世界比较重要的概念

Main-Axis就是水平方向,从左到右,这也是默认方向Cross-Axis是垂直方向,从上往下
flex-direction
属性值
1 | ul { flex-direction: row || column || row-reverse || column-reverse; } |
默认值是 row 它让 Flex 项目沿着 Main-Axis 排列(从左向右,水平排列) 这也解释了图二的效果
flex-wrap
属性值
1 | ul { flex-wrap: wrap || nowrap || wrap-reverse; } |
flex-flow
1 | flex-flow是flex-direction和flex-wrap两个属性的速记属性 |
语法
1 | ul { flex-flow: row wrap; } |
justify-content
接下来感受来自 flex 容器的魔法
它主要定义了 Flex 项目在 Main-Axis 上的对齐方式
属性值
1 | ul { justify-content: flex-start || flex-end || center || space-between || space-around } |
space-between
space-around
和 space-between 有点不同,第一个 Flex 项目和最后一个 Flex 项目距 Main-Axis 开始边缘和结束边缘的的间距是其他相邻 Flex 项目间距的一半
align-items
它主要用来控制 Flex 项目在侧轴上的对齐方式
1 | ul { align-items: flex-start || flex-end || center || stretch || baseline } |
baseline
效果类似 flex-start 但略有不同
区别就在于 baseline
align-content
1 | ul { align-items: stretch|center|flex-start|flex-end|space-between|space-around} |
stretch
flex-end
flex-start

center
Flex 项目属性
1 | order || flex-grow || flex-shrink || flex-basis |
order
允许 Flex 项目在一个 Flex 容器中重新排序。基本上,你可以改变 Flex 项目的顺序,从一个位置移动到另一个地方而不改变源代码,所有 Flex 项目的 order 值都是 0,Flex 项目会根据 order 值从低到高重新排序
1 | .oul li:nth-child(1) { |
Flex 项目 2、3、4 的 order 值为 0。现在 Flex 项目 1 的 order 值为 1

Flex 项目 2、3和4的order 值都是 0。HTML 源代码秩序并没有修改过。如果给 Flex 项目 2 的 order 设置为 2
可见 它也增加堆栈。现在代表 Flex 项目的最高的 order 值
当两个 Flex 项目具有相同的 order 值呢?在下面的示例中,把 Flex 项目 1 和 3 设置相同的 order 值。
现在仍是从低到高排列。这次 Flex 项目 3 排在 Flex 项目 1 后面,那是因为在 HTML 文档中 Flex 项目 3 出现在 Flex 项目 1 后面。
如果两个以下 Flex 项目有相同的 order 值时,Flex 项目重新排序是基于 HTML 源文件的位置进行排序
flex-grow 和 flex-shrink
- flex-grow :控制
Flex项目在容器有多余的空间如何放大(扩展)默认值是 0 表示开关是关闭的,即如果存在剩余空间,也不放大
1 | .item { |

属性值为 1 时
现在 Flex 项目扩展了,占据了 Flex 容器所有可用空间。也就是说开关打开了。如果你试着调整你浏览器的大小,Flex 项目也会缩小,以适应新的屏幕宽度
如果所有项目的 flex-grow 属性都为 1,则它们将等分剩余空间(如果有的话)
如果一个项目的 flex-grow 属性为 2,其他项目都为 1,则前者占据的剩余空间将比其他项多一倍
- flex-shrink:属性定义了项目的缩小比例,默认为
1(默认开启),即如果空间不足,该项目将缩小
1 | .item { |
flex-basis
它用于设置或检索弹性盒伸缩基准值浏览器根据这个属性,计算主轴是否有多余空间一般配合 flex-wrap 一起使用,flex 容器根据 flex-basis 计算是否需要换行
一些特性
1. 它的属性值可以是长度单位 (em || rem || px)** 或百分比 (%)**,百分比是按照父元素的 width 为标准
2. 默认值为 auto MDN
1 | 取值为**auto**时,它的值就等于当前项的**width**(或者默认的大小,width没有设置的话)" |
当 flex-item 没有自身宽高,其默认大小由 flex-basis 决定
即优先级: flex-basis > width (非 auto)
1 | .oul2-li{ |

4. 当元素存在默认宽高(input)
并且设置了 flex-basis,那么它的初始大小以固定宽高为下限,如果 flex-basis 超过了固定宽高,那么以 flex-basis 设置大小为准,如果 flex-basis 比固定宽高小,那么以固定宽高为准
1 | .Myinput { |

当将 flex-basis 设置的比默认宽度大

当将 flex-basis 设置的比默认宽度小 100
5. 当元素存在 min-width [height] 或者 max-width [height]
1 | 如果 `flex-basis` 的值 大于 `min-width[min-height]`,`flex-item content`的值为 `flex-basis` |

6. 元素设置 width[height]: auto;
CSS 解析器对比两者的值,两者谁大取谁作为 item 的基本尺寸,如果一个 item 没有内容,flex-item 初始大小就会以 flex-basis 来决定
但是如果 item 有了内容,且内容撑开的尺寸比 flex-basis 大,那么 flex-item 初始大小就会以 width[height]: auto; 来决定
优先级:width[height]: auto == flex-basis
1 | <ul class="oul2"> |

绝对和相对 Flex 项目
绝对 Flex 项目的宽度只基于 flex 属性,而相对 Flex 项目的宽度基于内容大小
相对项目
1 | .oul { |

Flex 项目的初始宽度是被自动计算的(flex-basis: auto),然后会伸展以适应可用空间(flex-grow: 1)
像这样 当 Flex 项目因为被设置为 flex-basis: auto,而导致宽度被自动计算时,是基于 Flex 项目内包含的内容的大小而计算的就是相对项目
绝对项目
1 | .oul li { |

Flex 项目的初始宽度是零(flex-basis: 0),并且它们会伸展以适应可用空间。当有两到多个 Flex 项目的 flex-basis 取值为 0 时,它们会基于 flex-grow 值共享可用空间
flex 组合属性
flex 是 flex-grow、flex-shrink和flex-basis 三个属性的速记(简写)顺序缩写为 GSB
一些取值规律
当 flex 取值为一个非负数字,则该数字为 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%,例如:flex: 1; 相当于
1 | li { |
⚠️ flex-basis的默认值为 auto,为什么此时是 0%?
当你创建一个 flexbox 上下文而不给 flex 项目设置任何属性,此时的默认值
此时它是相对项目
一旦你设置了 flex:1 简写属性
参考 MDN 所说
浏览器使其变成了绝对项目
flex: auto;
1 | li { |
当 flex 取值为一个长度或百分比,则视为 flex-basis 值,flex-grow 取 1,flex-shrink 取 1,有如下等同情况(注意 0% 是一个百分比而不是一个非负数字)
1 | .item-1 {flex: 0%;} |
当 flex 取值为两个非负数字,则分别视为 flex-grow 和 flex-shrink 的值,flex-basis 取 0%,如下是等同的:
1 | .item {flex: 2 3;} |
当 flex 取值为一个非负数字和一个长度或百分比,则分别视为 flex-grow 和 flex-basis 的值,flex-shrink 取 1,如下是等同的:
1 | .item {flex: 2333 3222px;} |
深入 flex
到这里 关于 Flex 的基础知识已经结束了,你可以用它们处理几乎任何问题
但是
Flexbox 是如何弹性的计算子级项目的大小的,它有没有什么规则
令我费解
好了,小🌻课堂开始了
flex 是应用在 X 轴和 Y 轴上的
每一根轴都包括三个东西 维度、方向、尺寸
- 维度:子元素横着排 (X 轴) 还是竖着排 (Y 轴)
- 方向:子元素的顺序 (顺序还是逆序)
- 尺寸:即父元素的
width,子元素在当前轴方向所占的位置的总和
如下图所示(来自 W3C 规范)

FFC(flex formatting context)
Flexbox 布局新定义了格式化上下文,类似 BFC(block formatting context)
定义了 display: flex; 或 display: inline-flex 的元素,和 BFC 一样,不会被浮动的元素遮盖,不会垂直外边距坍塌等等
与 BFC 的细微区别
- vertical-align 对 Flexbox 中的子元素 是没有效果的
- float 和 clear 属性对 Flexbox 中的子元素是没有效果的,也不会使子元素脱离文档流 (但是对 Flexbox 是有效果的!)
- Flexbox 下的子元素不会继承父级容器的宽
flex item(flex 子元素)
CSS 解析器会把 定义了 display: flex; 和 display: inline-flex; 的 Flexbox 下的子元素外部装进一个看不见的盒子里,我们通过排列这些盒子来达到排序、布局、 伸缩的目的
flex-item-size 是如何计算的
子元素的尺寸为主轴方向上元素的的自身宽度 再加上自身的 margin 、 border 和 padding
W3C 规范中介绍了 flex-item content 的计算规则
隐藏属性对 items-size 的影响
针对 display: none; visibility: hidden; visibility: collapse; transform: scale; 进行测试
结论
- 如果设置了
visibility: hidden; | visibility: collapse; | transform: scale;的flex-item content依然被算进主轴尺寸,CSS 解析器依然将可用空间分配给他们 - 如果设置了
display: none;CSS 解析器不会对该item的空间进行计算
关于 position: absolute 对 item 影响
position: absolute 也是适用 Flexbox 中的子元素的,并且,设置了 position: absolute 属性的子元素,也会受到 Flexbox 排列的影响
absolute 的子元素重叠在了一起,但是依然会受到 align-items: center; 的影响而居中

并且根据一系列的实验得知
flexbox 下设置了 absolute:
- flexbox 流下面的
justify-content和align-items item的top、left、right、bottommargin自始至终都会影响item的位置- 脱离了文档流的
item不会影响正常的flex布局
小结justify-content、align-items 和 top、left、right、bottom 都是位置属性 且 top、left、right、bottom 的值会覆盖 justify-content、align-items 的值
margin 的优先级是和 top、left、right、bottom 一样的,也就是说 margin 和 top、left、right、bottom 所设置的值会同时生效
flex-basis、flex-grow、flex-shrink 以及相应的计算
这三个属性只有父级元素设置了 display: flex | inline-flex; 才会生效,并且只针对主轴方向生效
- 如果 主轴是水平的,即
flex-direction: row; 那么flex-basis、flex-grow、flex-shrink控制的就是单个item的宽度 - 如果 主轴是垂直的,即
flex-direction: column; 那么flex-basis、flex-grow、flex-shrink控制的就是单个 item 的高度
那么所有 items 都会在主轴方向上的一条线上排列,CSS解析器会计算 items 在主轴方向上所占的空间 相对于 Flexbox 在主轴方向的所占的空间进行比较计算
- 如果
items所占的空间是小于Flexbox的 那么说明Flexbox还没有填满,CSS解析器就会计算还有多少空间没有填满,根据每一个item所设置的flex-grow设置的值,将这些空间分配按比例分配给每一个item

- 如果
items所占的空间是大于Flexbox的 那么说明Flexbox被填满了,CSS解析器就会计算超出了多少空间,根据每一个item所设置的flex-shrink设置的值,将这些空间分配按比例缩小每一个item
超出的空间是如何计算的
flow-grow 的计算流程
1 | 可用空间 = 将flexbox-content - 每个item-size的总和 |
将元素设置的 flow-grow 值加起来设置为 growSize单位分配空间 = 可用空间/growSize
然后真正分配的时候根据自己的比例计算增加的值应该增加的值 = 自己的grow值 * 单位分配空间

flow-shrink 的计算流程
它的流程与 flow-grow 的计算流程不同
1 | shrink比例 = flex-shrink * item-size / 之前的总和 |
如图所示

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

Flexbox 的浏览器支持
让我们求助于 caniuse
总结
深入理解 Flex 还是挺不容易的。