从盒子宽度开始讲起的flex布局那些事(一)
背景
事情的背景是同事在分享flex布局时聊到了一个最小内容尺寸导致子项未均分的场景,恰好自己对这方面没有过多的去深入了解。所以刚好可以借着这个case来探究一下flex布局计算的那些原理。
首先,我们需要来了解一下导致溢出的根本原因所在:最小内容尺寸。
本文会在研究主线问题的同时对了解到的衍生知识点也做研究,所以总体下来会有点发散。后期会找时间附上一张思维导图。
最小宽度尺寸与最小内容尺寸
min-width
min-width
用于设置一个元素的最小宽度,当设置的min-width
大于width
时,会阻止width
属性的应用值小于我们min-width
设置的值(不会阻止max-width
的应用值)。
意思很简单,就是当我们设置min-width
为200px
时,如果我们的width
为199px
,那么实际应用的宽度值会是200px
,这个大家都懂。
在这里提到了一个应用值的概念,在MDN上对他的定义是一个css属性完成所有计算后最终使用的值,先忽略掉主线,来看看这个最终值的得出,具体来说,计算最终值会有三个步骤:
首先,拿到指定值,再按计算值规范算出计算值,最后计算布局,得到最终的结果,也就是应用值。在这个流程里其实可以分出几个概念:指定值、初始值、计算值、应用值、解析值、实际值。
CSS值计算流程
指定值
指定值主要是取自样式表,在当前文档中的样式表中
-
如果对某个属性赋值,例如设置
color:red
则这个red
会被优先使用为color的属性值,当然这个会受CSS选择器权重影响。优先级:内联>ID>类>标签,具体计算是依据A,B,C,D来决定的,这个ABCD的A指的是内联样式的出现次数,B指的是ID选择器出现的次数,C指的是类选择器、属性选择器、伪类的出现次数,D指的是标签选择器和伪元素的出现次数,如果存在两个优先级,则会从左到右依次去比较ABCD,较大者获胜,如果相等,则比较下一位,如果都相同,则后者覆盖前者。
-
如果没有设置某个属性值,那么会尝试从父元素(沿着元素的父链向上查找)那继承。例如字体颜色大小等等,当然也不是所有的属性都能被继承,例如一些盒模型属性就无法被继承,例如
margin
、padding
。
初始值
CSS属性的初始值是其默认值,初始值的使用取决于属性是否被继承:
- 对于继承属性,初始值只能被用于没有指定值的根元素上。说人话就是指定值如果继承到了,那么初始值就不生效.
- 而对于非继承属性,初始值可以被用于任意没有指定值的元素上。说人话就是如果指定值没有可继承的属性值,那么会采取这个元素的这个属性的初始值作为结果。例如
width
初始值为auto
。
每个元素的各个属性的初始化都会存在差异,可以在MDN查到。
计算值
计算值会去处理一些预设的特殊值(例如inherit
、initial
),以及进行计算,将一些相对值变为属性摘要中计算值行描述的值,通常包括将相对值转换为绝对值,但对于一部分需要布局后才能知道具体的值的属性来说,这部分值中的相对值会在应用值确定之后转换为绝对值。所以在这个过程中其实也会存在着一些相对值,并不是所有的属性对应的值都会转变为绝对值,主要还是看是否满足计算值描述范畴。
对于
line-height
属性值来讲,如果是没有单位的数字,则该值就是其计算值。
例如对于width
来说,他的计算值描述为一个百分比或者auto
或者一个绝对的长度。他是允许百分比这样相对计算值存在的。
如果我们将width
设置为em
,那么会将其转换为绝对值px
。如果将其设置为50%
,则会将其转换为百分比的计算值,需要确定布局之后才能够这个知道具体的值。
应用值
应用值就是完成所有计算后最终使用的值,之前提到的百分比的计算相对值也会在这里得到实际的绝对值,例如:
- 没有明确的宽度。指定的宽度: auto (默认). 计算值对应宽度: auto. 应用的宽度: 200px;
- 明确的宽度: 50%. 指定的宽度: 50%. 计算的宽度: 50%. 应用的宽度: 100px;
- 明确的宽度: inherit. 指定的宽度: 50%. 计算的宽度: 50%. 应用的宽度: 100px;
解析值
这个值是 getComputedStyle()
返回的值。对于大多数属性,它是计算值computed value
,但对于一些旧属性(包括宽度和高度),它是使用值used value
。
实际值
一个css属性他的属性值应用起来可能会受一些浏览器环境影响,从而需要表现出不同的实际行为,例如在某些浏览器只能显示整数类型的长度,即使应用值算出来是100.2px
,但实际展示则会取整。而有些游览器又采取四舍五入。所以实际值表示的是实际渲染出来的值。他与应用值的差异主要是受浏览器环境影响。
下面也给出了getComputedStyle
来获取width的应用值的一个小demo。
给出这个demo是为了回归主线,说明当我们设置
min-width
为200px
时,如果我们的width
或者max-width
为199px
,那么实际应用的宽度值会是200px
。
可以看到,width
实际的应用值变为了200px
,而max-width
的应用值并未受到min-width
影响而发生改变。
min-content
min-content
则表示的是内容的最小固有尺寸,对于文本内容而言,这意味着内容会利用所有软换行的机会,变得尽可能的小。在MDN上对于它还提到了一句话:可以使用 interpolate-size
属性和 calc-size()
函数来启用由 min-content
开始或结束的动画。
当没有显式地设置盒子的尺寸时(比如没有设置width
,height
等这些外部尺寸),那么一般会以元素盒子内部的内容(也就是固有尺寸)来决定其元素尺寸大小。由于这个大小是根据内容来决定的。而内容其实会分为两种,一种是纯文本,另一种是文本带图片。
如果是文本带图片,即元素的内容是图文混合,那么它的最小内容尺寸会等于图片的尺寸。
而对于纯文本来说,他会受到样式(font-size
、font-weight
等)以及字符种类的影响。对于汉字字符,他的最小内容尺寸就是单个文字的尺寸。而对于英文字符,他的最小内容尺寸是由连续的英文字符决定(例如一个单词),但并不是所有的英文字符都会组成连续单元,一般会终止于空格或者一些标点符号等等。
这个也可以通过设置样式
word-break:break-all
来使得英文字符的表现形式与中文字符一样,以单个英文字符为最小尺寸。
固有尺寸
基于其内容的尺寸,不考虑它所在的上下文影响, 元素的固有尺寸由其 min-content 和 max-content 尺寸表示
软换行
软换行换行不换段,是排版自动产生的换行,在页面格式发生变化时自动调整。硬换行则换行又换段,一般是人工手动换行(例如Enter)。
interpolate-size
interpolate-size
用于改变插值计算的规则,可以将一些尺寸关键字(例如auto)也改为插值计算,没错说到这里我们就知道了一种新的高度展开渐变的实现方式
它有两个属性值,一个是numeric-only
表示仅限数值,也就是只有真实的数值才会有过渡效果(目前浏览器的默认效果),第二个allow-keywords
表示允许所有关键词,当然包括auto
属性。
calc-size
calc-size
可以将一些关键词转换为具体尺寸,例如在下面的换行demo中,我们使用height: calc-size(auto);
就可以实现过渡效果,原因是calc-size
将auto
转换为了一个具体的值,而值与值之间过渡是能生效的,calc-size
不仅可以对auto
使用,还可以对min-content、max-content、fit-contet
使用,也可也像calc
一样支持计算,例如calc-size(auto + 10px)
不过这两个目前只有129版本以上的chrome和edge支持了(calc-size在edge高版本游览器似乎还是没生效,但看can i use上是支持了的),其他浏览器还没有支持,所以还是需要慎用。
下面的demo如果没生效就表示当前浏览器环境不支持
min-width与min-content区别
经过上面的分析,我们知道min-width
是一个属性,用于表示元素盒子的最小宽度尺寸,而min-content
则为尺寸关键字,它的计算值表示最小内容尺寸。这里会有两个概念,最小尺寸和最小内容尺寸。
- 最小尺寸一般指的是
min-*
的属性 - 最小内容尺寸则指的是
min-content
的计算值
可以理解为一个是属性,一个是值。
值得注意的是最小尺寸不会小于最小内容尺寸。这个挺好理解的,例如最小内容尺寸为50px,而我的最小尺寸设置为30px,这时盒子的实际宽度会为50px。
flexBox和Grid中的min-width与min-content区别
在W3C规范中有提到min-width
的默认值为auto
,但浏览器对于width:auto
与min-width:auto
的计算是不一样的,具体来说,在盒子上下文格式没有发生改变时,min-width
取值为auto
时,浏览器对其计算出来的值为0,但当我们使用网格(网格格式化上下文)或者弹性布局(弹性格式化上下文)的时候我们会改变盒子的上下文格式(display:[inline-]flex/grid
),这时对于子项目来说min-width:auto
的计算就不再是0而是关键字auto
。而在 Flexbox 和 Grid 布局中,Flex 项目和 Grid 项目的最小尺寸不会小于其最小内容尺寸(min-content
),也就是在这也会导致一些超出预期的问题出现。
例如当我们的子项的最小内容尺寸大于均分下的尺寸,这时就会导致内容较大的盒子所分得的空间变大,从而无法实现均分,例如下面的测试文本,左边这一个由于最小尺寸不能小于最小内容宽度,所以导致他在空间分配时,获得了更多的空间,从而无法实现均分,而右边在手动设置最小宽度为0之后,实现了均分。背景中的问题也得到了解释。
总结
在CSS计算值的过程中,会存在着许多概念和流程,首先,拿到指定值,再按计算值规范算出计算值,最后计算布局,得到最终的结果,也就是应用值。在这个流程里其实可以分出几个概念:指定值、初始值、计算值、应用值、解析值、实际值。
指定值主要是取自当前文档中的样式表,如果对某个属性赋值,则会被优先使用,并且会受选择器权重影响。如果没有设置某个属性值,那么会尝试从父元素(沿着元素的父链向上查找)那继承(不是所有的属性都能被继承),如果继承到了,那么自己本身属性的初始值就不生效,如果没有继承到,那么会以这个元素的这个属性的初始值作为结果去用于计算。而计算值会去处理一些预设的特殊值以及将一些相对值变为属性摘要中计算值行描述的值,通常包括将相对值转换为绝对值(一部分相对值需要等到应用值确定之后才能转换为绝对值),主要还是看是否满足计算值描述范畴。
应用值就是完成所有计算后得到的绝对值,解析值用于返回得到应用值(也可能是计算值),最后根据不同的浏览器环境得到最终的实际值。
对于min-width和min-content来说,最小尺寸一般指的是min-*
的属性,他是一个属性。最小内容尺寸则指的是min-content
的计算值,他是一个值。最小尺寸不会小于最小内容尺寸。
Flex项目收缩之后的宽度不能小于 Flex 项目的最小内容尺寸,所以在使用flex:1或者网格轨道设置1fr
时的时候,记得加上min-width: 0
或者overflow: hidden;
,而且使用minmax(0, 1fr)
来替代 1fr
。这样编写出的 CSS 更具防御性,代码也更健壮。