一、背景
到目前为止,Flexbox布局应该是目前最流行的布局方式之一了。每位开发者都熟悉却又陌生,Flexbox布局的最大特性就是让Flex项目可伸缩,也就是让Flex项目的宽度和高度可以自动填充Flex容器剩余的空间或者缩小Flex项目适配Flex容器不足的宽度。而这一切都是依赖于Flexbox属性中的flex属性来完成。一个Flex容器会等比地按照各Flex项目的扩展比率分配Flex容器剩余空间,也会按照收缩比率来缩小各Flex项目,以免Flex项目溢出Flex容器
部分内容引用自大貘老师的「聊聊Flexbox布局中的flex的演算法」
这篇文章就详细且深入地了解一下 Flexbox 布局中如何拓展和收缩
ps:本文中有对之前优秀文章的较多引用,因为目的是为了讲清楚这件事情
二、基本使用
2.1 如何使用
Flexbox 的基础使用不再赘述,这里只关注和宽度计算相关的内容,所以先来看一下涉及到的语法,也就是 flex属性
从 MDN 上的文档可以看出,我们常用的 flex属性其实是flex-grow、flex-shrink、flex-basis 的缩写。而 flex的默认值是:flex**: 0 1 auto**
也就是默认不会撑开,但是会收缩,demo-FlexDefault如下:
2.2 剩余空间和不足空间
在Flexbox布局中,Flex容器中包含一个或多个Flex项目(该容器的子元素或子节点)。Flex容器和Flex项目都有其自身的尺寸大小,那么就会有:Flex项目尺寸大小之和大于或小于Flex容器 情景:
-
当所有Flex项目尺寸大小之和小于Flex容器时,Flex容器就会有多余的空间没有被填充,那么这个空间就被称为Flex容器的剩余空间(Positive Free Space)
-
当所有Flex项目尺寸大小之和大于Flex容器时,Flex容器就没有足够的空间容纳所有Flex项目,那么多出来的这个空间就被称为负空间(Negative Free Space)
部分内容引用自大貘老师的「聊聊Flexbox布局中的flex的演算法」
这里的内容已经比较好理解,就不举具体的例子来阐述了
2.3 不足空间宽度如何计算
这里主要就是flex-shrink 生效,以demo-FlexDefault为例,容器宽度 100,左边容器宽度 50,右边容器宽度 70,左右容器都是默认属性,也就是:flex**: 0 a auto**,也就是两个元素按照 1:1 的比例收缩,计算如下:
ini
// 不足空间:50 + 70 - 100 = 20
// 20的空间左右两边1:1认领
// 因为左右两边都有边框,所以左右两边实际能收缩的大小:48 + 68 = 116
// 那左边的宽度: 48 - 20 / 116 * 48 + 2 = 41.724137931034484
// 实际截图宽度是:41.73,基本符合,差的是精度,取决于chrome内部精度处理
浏览器的 pixel 最小单位称为 LayoutUnit,Chrome 为 1/64px,Firefox 为 1/60px
但是按照这个精度计算还是和 chrome 算出的结果有一点差异,这里暂时不再深入
上面是 flex-shrink 比例相同的场景,也可以配置 flex-shrink 比例不同,例如demo-FlexShrink,容器宽度 100,左边容器宽度 50,flex-shrink 是 1, 右边容器宽度 70,flex-shrink 是 2
计算如下:
ini
// 不足空间:50 + 70 - 100 = 20
// 20的空间左右两边1:2认领
// 因为左右两边都有边框,所以左右两边实际能收缩的大小:48 * 1 + 68 * 2 = 184
// 那左边的宽度: 48 - 20 / 184 * 48 * 1 + 2 = 44.78260869565217
// 实际截图宽度是:44.78,基本符合,差的是精度,取决于chrome内部精度处理
2.4 剩余空间宽度如何计算
这里主要就是 flex-grow 生效,flex-grow 的逻辑 就比较简单了,就是按照 flex-grow 的比例去分配剩余空间,详见demo-FlexGrow:
总结下来大致规则如下:
ps:浏览器在实际实现的时候会比这个要复杂,特别是 flex-sgrink 之后会小于 0 的场景,具体可以参考大貘老师的「聊聊Flexbox布局中的flex的演算法」
2.5 Flex 项目宽度如何计算
上面的计算里面主要依赖 Flex 容器宽度和 Flex 项目宽度, Flex 容器的宽度比较好计算,就是 dom 的真实宽度,但是 Flex 项目的宽度如何计算,上面的示例里面用了width来控制宽度,这里要说到的是 flex-basis 属性,看下面的demo-FlexBasis:
从上面可以看到,对于「我是宽度 50」这个 div,尽管设置的宽度是 50,但是通过修改 flex-basis 属性,发现真实宽度会发生变化,这一点在 [MDN]( developer.mozilla.org/zh-CN/docs/...](developer.mozilla.org/zh-CN/docs/...) 上也有介绍:
上面的是最终结果,但是怎么理解背后的原因?其实一句话来说决定 Flex 项目宽度的只有 flex-basis。
下面大部分内容摘自「Oh My God,CSS flex-basis原来有这么多细节」
此时一定会有人反驳,我明明设置了width:100px就有效果啊!
对是有效果,但并不是width直接生效的,而是flex-basis的作用。
深入理解flex-basis:auto
flex-basis的默认值是auto,表示自动,也就是完全根据子列表项自身尺寸渲染。
自身尺寸渲染优先级如下:
arduino
min-width > || max-width > width > Content Size
同时,在Flexbox 布局中,flex-basis优先级是比width高的(可以理解为覆盖)。
所以,flex-basis和width同时设置了具体的数值,则width属性值直接被打入冷宫,在样式表现上完全被忽略。
实际上,flex-basis值是auto的时候,width属性值也是被打入冷宫的,只是,这个时候,由于后宫放权,所以,冷宫也是有影响力的。什么意思呢?
flex-basis:auto的含义是,子项的基本尺寸根据其自身的尺寸决定。而这个自身尺寸与下面这几个方面有关:
-
box-sizing盒模型(这个已经介绍过了);
-
width/min-width/max-width等CSS属性设置;
-
content内容(min-content最小宽度);
所以下面两段Flex布局中的CSS就不难理解了:
css
item-width {
width: 100px;
}
最终尺寸 = 自身尺寸 mix basis尺寸。
此时,自身尺寸为100px,basis尺寸按照自身尺寸来算(因为此时属性值是auto),因此最终尺寸是100px。
css
item-basis {
flex-basis: 100px;
}
最终尺寸 = 自身尺寸 mix basis尺寸。
此时,自身尺寸为content内容最小宽度,basis尺寸100px,因此最终尺寸是:如果content内容最小宽度不超过100px,则最终尺寸是100px,否则就是内容最小宽度。这里内容最小宽度也是坑比较多的,或者说比较难理解的一部分,这里先不展开。
三、深入理解
到这里其实都是比较好理解的,算是都是正常的部分,但是实际上在使用 Flexbox 布局的时候会遇到各种问题,特别是溢出后不会省略的问题,例如demo-FlexOverflow:
上面这个 demo 基本上是大家遇到问题时的最简复现 demo, 可以看到,右边并没有被收缩,宽度是内容宽度 306px。
3.1 几种解法
上面这个问题有一些常见"解法",这里分别展示一下:
一、width
demo-FlexOverflowSoloutionWidth:
二、overflow
demo-FlexOverflowSoloutionOverflow:
三、min-width
demo-FlexOverflowSoloutionMinWidth:
四、white-space
demo-FlexOverflowSoloutionWhiteSpace:
虽然看起来有四种解法,但是实际上真正算解法的只有两种,分别是 overflow 和 minWidth,其他两种不能算解决方案,但是对理解这个原理很有帮助,并且确实 UI 发生了变化,所以这里一起列了出来。
-
width 解法失去了flex-grow的能力,且实际场景中无法知道这里width的值该是多少
-
white-space解法实际上不符合要求,因为文字并没有收缩且省略,而是换行显示
3.2 规范解读
说到这里其实还是比较迷糊,为什么不会收缩、每种"解法"为什么就能带来变化甚至符合预期、每种解法区别又是什么?这些问题的答案都需要从规范中寻找答案。
Why don't flex items shrink past content size?这个 stackoverflow 的答案解释的比较详细
大概翻译过来每个 Flex Item 其实都有默认的 min-width 和 min-height,min-height 暂时先不看,这里先只关注 min-width。
上面其实提到过,自身尺寸渲染优先级如下:
arduino
min-width > || max-width > width > Content Size
其实大部分人应该都没关注过这里的min-width,但是实际上它的优先级却是最高的,这里看
容器宽度 200,左边内容设置了 30 宽度,但是实际上宽度是 100,就是因为 min-width 限制了最小宽度,回到一开始不能理解的溢出不省略场景,demo-FlexOverflow:
是不是可以理解为右侧额外加了 minWidth: 306 的属性
3.3 理解解法
继续看什么时候 Flex Item 会添加默认的 min-width
这篇回答比较早,实际上最新的规范对这里的描述已经发生了改变,但是意思其实差不多,点击查看最新规范
总结一下面就是:
-
当 overflow 是 visible,min-width值是 auto
-
当 overflow 不是 visible 的时候,min-width 的值是 0
这里就解释了解法 2 和解法 3 的原理,实际上都是影响了 min-width,解法 2 是添加 overflow 改变默认添加的 min-width 值,而解法 3 是直接覆盖默认的 min-width 值
3.4 min-content
但是到这里解法 1 和解法 4 还是没有涉及到,或者更具体一点 min-width为auto的时候这个宽度到底是什么?规范关于这里的解释比较晦涩,需要对 css 规范有比较深的了解才能较好理解
这里个人觉得可以用 min-content 来理解:
部分内容引用自大貘老师的「聊聊Flexbox布局中的flex的演算法」
CSS可以给任何一个元素显式的通过width属性指定元素内容区域的宽度,内容区域在元素padding、border和margin里面。该属性也是CSS盒模型众多属性之一。
如果我们显式设置width为关键词auto时,元素的width将会根据元素自身的内容来决定宽度。而其中的min-content和max-content也会根据元素的内容来决定宽度,只不过和auto有较大的差异
-
**min-content**: 元素固有的最小宽度(内容)
-
**max-content**: 元素固有的最大宽度(内容)
比如下面这个示例:
如果内容是英文的话,min-content的宽度将取决于内容中最长的单词宽度,中文就有点怪异(其中之因目前并未深究),而max-content则会计算内容排整行的宽度,有点类似于加上了white-space:nowrap一样。
更进一步,大家可以在demo-FlexDefault这个 demo 中去尝试看一下每个 Flex Item 到底能压缩到什么程度。所以这里回答解法 1 和解法 4 的原理:
-
解法 1 设置了width会让 min-width不再基于min-content
-
解法 4 设置了white-space:initial,实际上就是改变了min-content的计算方式
3.5 总结
这里涉及到的概念这么多,那该怎么理解呢?总结如下:
-
Flex Item 的宽度只受 flex-basis的直接影响,但是flex-basis值为auto时又受width影响
-
Flex Item 的宽度还受min-width的限制
-
overflow为visible的 Flex Item 的min-width默认为auto,此时收到width以及min-content影响
四、总结
大部分人使用 Flexbox 布局的时候都是岁月静好,直到遇到了 Flex Item 宽度超过 Flex 容器宽度的时候,就遇到知识盲区,感叹 css 真是不讲道理,捉摸不透,实际上也的确如此,css 相比 js 更难理解,主要还是大部分人都是站在使用角度,就像在 js 中使用一个第三方库一样,只知道接口,不知道底层怎么实现,那一旦出问题就无法理解、排查。
希望这篇文章能带大家更深入理解 Flexbox 布局,更重要的是下次遇到相关问题从原理上去寻找解法!
相关参考: