前言
自从 chrome 90 更新后,devtool 专门为 flexbox 添加了一个调试工具,只需要点击 display: flex;
后的小按钮就可以打开一个小面板,可以直接添加对应的 flex 样式:
但是仅仅如此么?当然不是,这个更新最大的改动,就是为页面中的 flex 布局添加了专属的显示效果。不知道你在开发时有没有注意到下图这样的紫色斜线条纹:
这些紫色斜条纹区域代表了 由于 flex 布局而产生的间隙,这些区域里不会存在 flex 子项的。除此之外,还有一个下面这样的效果可能会让人有点摸不着头脑:
这种代表着 主轴 flex 子项的缩放效果,众所周知,flex 元素上可以通过 flex-grow
和 flex-shrink
指定子项在存在剩余空间 / 空间不足的情况下如何进行缩放,而上图中 紫色虚线区域代表了该子项原本的大小,箭头表示经过 flex 缩放后的元素实际大小。
然而,并不是所有的 flex 子项都会显示一个对应的紫色虚线块。例如下面这个例子:
<style>
.flex-box {
display: flex;
width: 300px;
}
.flex-box div:nth-child(1) {
background-color: #ffd200;
}
.flex-box div:nth-child(2) {
background-color: #ff7b00;
}
.flex-box div:nth-child(3) {
background-color: #00ff6a;
}
</style>
<div class="flex-box">
<div>box1</div>
<div>box2</div>
<div>box3</div>
</div>
当我们把鼠标移动到三个子元素上时,发现都没有显示出之前那种虚线区域!实际上,当长度不为绝对值时,flex 都不会显示对应的虚线区域。chrome 为什么要做这个奇怪的区分呢?想回答这个问题,我们要先看一下下面这两个例子:
<style>
.flex-box {
display: flex;
width: 300px;
}
.flex-box div:nth-child(1) {
background-color: #ffd200;
width: 100px;
}
.flex-box div:nth-child(2) {
background-color: #ff7b00;
width: 100px;
}
.flex-box div:nth-child(3) {
background-color: #00ff6a;
width: 200px;
}
</style>
<div class="flex-box">
<div>box1</div>
<div>box2</div>
<div>box3</div>
</div>
我们把父容器的主轴长度指定为 300,三个子元素长度分别为 100、100、200。由于子元素的默认 flex-shrink 值为 1,即等比缩小。因此,这三个元素将按比例“平分”这超出的 100 像素,即三个容器分别缩短 25、25、50 像素。
事实上也的确如此,可以看到 chrome 已经绘制出了每个子元素对应的阴影区域。每个元素缩短的长度也和我们预料的一样。那么下面这个例子呢?注意其中 box3 的宽度从显式从 width: 200px
变成了默认的 width: auto
。
<style>
.flex-box {
display: flex;
width: 300px;
}
.flex-box div:nth-child(1) {
background-color: #ffd200;
width: 100px;
}
.flex-box div:nth-child(2) {
background-color: #ff7b00;
width: 100px;
}
/* 第三个盒子没有指定宽度,而是由其内部元素将其“撑开” */
.flex-box div:nth-child(3) {
background-color: #00ff6a;
}
.flex-box div:nth-child(3) div {
width: 200px;
}
</style>
<div class="flex-box">
<div>box1</div>
<div>box2</div>
<div>
<div>inner box3</div>
</div>
</div>
实际显示结果如下,可以看到,chrome 没有绘制 box3 的紫色阴影块,并且 box3 也没有被缩短!
惊了怎么会这样?答案要从 flex 执行缩放的流程说起,大致如下:
其实关键就在于 需要先确定子元素的具体长度,然后才能对剩余空间进行分配。也就是说,我们的 box3 的宽度是在第二步的时候就确定的,这时还没有计算剩余宽度:
那么问题来了,如果 box3 也参与最后的剩余空间分配的话,那是不是也要同时调整它的子元素?但是 box3 的宽度就是由其子元素提供的,如果子元素尺寸跟着调整的话,就会陷入“计算子节点长度 > 重新分配剩余空间 > 修改长度 > 子项内部元素调整 > 重新计算子节点长度”这种死循环了。所以说,长度属性为 auto
的子元素将不会参与最后的弹性缩放。
现在让我们回到最开始的问题,为什么没有明确长度属性的 flex 子元素不会显示紫色虚线块呢?因为当长度为 auto 时,其缩放结果和长度为绝对值时是有可能不一样的。chrome 正是通过这个区别显示,告诉开发者有个 flex 子元素的尺寸没有明确指定,所以最终的缩放结果和可能会和预期有所出入。
紫色虚线块和 flex-basis
事实上,上面的紫色虚线块代表的其实就是对应元素的 flex-basis 值,可以看到,这个框的尺寸在大多数情况下是和元素本身的尺寸(width
、height
)是一致的,因为在默认情况下(flex-basis: auto
),它的实际值就是对应元素的主轴长度。这也解释了为什么在默认情况下,flex 进行主轴元素缩放时是等比缩放的(元素所占主轴越长,要进行的缩放也就越多)。
而且,相信很多人对这个属性一直不是很清楚,这个 basis 到底是什么的基础呢,这里直接说答案:flex-basis
就是 flex 进行主轴弹性缩放的基础值。
flex 执行缩放的过程很简单,上面已经提到了,这里精炼一下::
-
计算剩余空间:flex 会先确定每个子元素的
flex-basis
的实际值,然后用父容器的实际尺寸减去所有flex-basis
,得到的值就是剩余空间(为负说明要进行缩小)。 -
分配剩余空间:得到了剩余空间后,flex 会使用每个子元素的分配比率来确定其可以获得多少剩余空间份额。而这个分配比例,在剩余空间为正即为
flex-grow
、在剩余空间为负时则为flex-shrink
。
了解了这个,我们就可以明确下面这几种组合能产生的效果(还是以上面 100、100、200 的三个盒子为例):
例一
flex: 0 1 0;
这里我们将 flex-basis
设置为 0,也就是说 flex 将会把全部的空间都拿来分配,但是又因为 flex-grow
也是 0,所以三者并不会把多的空间分掉。
这里有个有意思的小细节,由于 flex-basis
只会用在弹性缩放中,哪怕他设置为 0 也不应该影响内容的正常显示,所以就算 flex-grow
也是 0,flex 也会分配给这个元素足够显示内容的长度(取决于其子元素“撑”起来的宽度和显式指定的 width
/ height
哪个小),所以在这个例子里,三个 box 都获得了足够其显示出子元素的主轴空间。
例二
flex: 1 1 0;
在 flex-basis
均为 0 时将 flex-grow
设置为 1,因为三者的 grow 都相同,所以剩余空间将被分为三等分。而三者的 basis 也都为 0 ,所以 “剩余空间”的实际值就是整个父容器空间。因此,三个 box 将会把整个父容器空间均分为三份。
例三
flex: 1 1 1;
注意哦,这里有个坑,我们可以在 flex-basis 取值 里看到,flex-basis
并不支持无单位数值,所以这个属性将被认定为无效属性,chrome devtool 里也会提示无效,从而展示默认缩放效果。
例四
flex: 1 0 0;
我们上面已经提到了,当剩余空间为负时,将使用 flex-shrink
作为缩放比率,而这里其值为 0,而缩放值乘以 0 还是 0,既每个元素都不进行缩小。因此子元素溢出了父容器。
上面简单的解释了一下新的 chrome flexbox devtool 以及相关的 flex 主轴弹性缩放细节,感兴趣的话可以用 chrome 90+ 的浏览器打开这个 flex 在线示例 尝试一下。最后咱们再来两个小问题加强一下:
问题1、定义了百分比长度的子容器
如下,父容器的长度为 300px,三个子元素的长度分别为 100px、100px、100%,问最后三者的实际长度分别为多少。
<style>
.flex-box {
display: flex;
width: 300px;
}
.flex-box div:nth-child(1) {
background-color: #ffd200;
width: 100px;
}
.flex-box div:nth-child(2) {
background-color: #ff7b00;
width: 100px;
}
.flex-box div:nth-child(3) {
background-color: #00ff6a;
width: 100%;
}
</style>
<div class="flex-box">
<div>box1</div>
<div>box2</div>
<div>box3</div>
</div>
解答:分别为 60px、60px、180px。首先会将 width:100%
转换为实际的长度,而其父容器存在确定的 width: 300px
,于是 box3 在缩放前的尺寸即为 300px。这里要注意的是,百分比 width
的元素是会参与弹性缩放的,因为他在确定实际长度时会往外去找父节点的大小,并不会出现 width:auto
那种死循环问题。
所以,最终就是 100、100、300 长度的三个 box 等分超出的 200px。最后就可以算出三个 box 分别缩短 40、40、120px。
问题2、form 表单项没对齐问题
这个问题是我写下这篇文章的诱因,如下是一个使用 flex 实现的多列表单的 demo,但是第二列的 label 和第一列的并没有对齐,如何解决?
<style>
div {
display: flex;
width: 100%;
margin-bottom: 10px;
}
span {
width: 7em;
text-align: right;
}
input {
width: 100%;
}
</style>
<div>
<span>问题1:</span><input type="input" />
</div>
<div>
<div>
<span>问题2:</span><input type="input" />
</div>
<div>
<span>问题3:</span><input type="input" />
</div>
</div>
如果能看懂 flexbox devtool 的话这个问题其实很好解决,我们把鼠标分别放到问题 1 和问题 2 的 label 上,可以看到:
这两者的实际主轴长度是相同的(紫色虚线区域大小相同),但是由于 flex 的主轴缩放导致了问题 2 label 缩短的长度更多(问题 2 的箭头更长)。因为箭头是朝内,所以说罪魁祸首就是 flex-shrink
的默认值 1。由于第二行容纳了四个元素,并且为了占满整行宽度,两个 input 的宽度都是 100%,导致第二行的两个 span 要承担更多的缩减。
解决方法很简单,把 label span 设置为 flex-shrink: 0
即可。
写在最后
flex 布局在日常开发中是经常用到的,但是一般都只是拿来调整对齐或者自适应,对 flex 子项的属性了解还是不够深入的。借这个机会深入了解一下,如果感兴趣的话可以看一下下面参考里加粗的两篇 MDN 文章,相信会加深你对 flex 的了解。