深入理解Flexbox布局的宽度计算

一、背景

到目前为止,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,但是实际上它的优先级却是最高的,这里看

demo-FlexMinWidth:

容器宽度 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 布局,更重要的是下次遇到相关问题从原理上去寻找解法!

相关参考:

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
唯之为之5 小时前
巧用mask属性创建一个纯CSS图标库
css·svg
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript