深入理解 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、bottom
margin
自始至终都会影响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 还是挺不容易的。