从 chrome flexbox devtool 探究 flex 的主轴元素缩放

前言

自从 chrome 90 更新后,devtool 专门为 flexbox 添加了一个调试工具,只需要点击 display: flex; 后的小按钮就可以打开一个小面板,可以直接添加对应的 flex 样式:

但是仅仅如此么?当然不是,这个更新最大的改动,就是为页面中的 flex 布局添加了专属的显示效果。不知道你在开发时有没有注意到下图这样的紫色斜线条纹:

这些紫色斜条纹区域代表了 由于 flex 布局而产生的间隙,这些区域里不会存在 flex 子项的。除此之外,还有一个下面这样的效果可能会让人有点摸不着头脑:

一块紫色区域带个向外的箭头
一块紫色区域带个向内的箭头

这种代表着 主轴 flex 子项的缩放效果,众所周知,flex 元素上可以通过 flex-growflex-shrink指定子项在存在剩余空间 / 空间不足的情况下如何进行缩放,而上图中 紫色虚线区域代表了该子项原本的大小,箭头表示经过 flex 缩放后的元素实际大小。

然而,并不是所有的 flex 子项都会显示一个对应的紫色虚线块。例如下面这个例子:



box1
box2
box3

当我们把鼠标移动到三个子元素上时,发现都没有显示出之前那种虚线区域!实际上,当长度不为绝对值时,flex 都不会显示对应的虚线区域。chrome 为什么要做这个奇怪的区分呢?想回答这个问题,我们要先看一下下面这两个例子:



box1
box2
box3

我们把父容器的主轴长度指定为 300,三个子元素长度分别为 100、100、200。由于子元素的默认 flex-shrink 值为 1,即等比缩小。因此,这三个元素将按比例“平分”这超出的 100 像素,即三个容器分别缩短 25、25、50 像素。

事实上也的确如此,可以看到 chrome 已经绘制出了每个子元素对应的阴影区域。每个元素缩短的长度也和我们预料的一样。那么下面这个例子呢?注意其中 box3 的宽度从显式从 width: 200px 变成了默认的 width: auto



box1
box2
inner box3

实际显示结果如下,可以看到,chrome 没有绘制 box3 的紫色阴影块,并且 box3 也没有被缩短!

惊了怎么会这样?答案要从 flex 执行缩放的流程说起,大致如下:

其实关键就在于 需要先确定子元素的具体长度,然后才能对剩余空间进行分配。也就是说,我们的 box3 的宽度是在第二步的时候就确定的,这时还没有计算剩余宽度:

那么问题来了,如果 box3 也参与最后的剩余空间分配的话,那是不是也要同时调整它的子元素?但是 box3 的宽度就是由其子元素提供的,如果子元素尺寸跟着调整的话,就会陷入“计算子节点长度 > 重新分配剩余空间 > 修改长度 > 子项内部元素调整 > 重新计算子节点长度”这种死循环了。所以说,长度属性为 auto 的子元素将不会参与最后的弹性缩放。


现在让我们回到最开始的问题,为什么没有明确长度属性的 flex 子元素不会显示紫色虚线块呢?因为当长度为 auto 时,其缩放结果和长度为绝对值时是有可能不一样的。chrome 正是通过这个区别显示,告诉开发者有个 flex 子元素的尺寸没有明确指定,所以最终的缩放结果和可能会和预期有所出入。

紫色虚线块和 flex-basis

事实上,上面的紫色虚线块代表的其实就是对应元素的 flex-basis 值,可以看到,这个框的尺寸在大多数情况下是和元素本身的尺寸(widthheight)是一致的,因为在默认情况下(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 都获得了足够其显示出子元素的主轴空间。

该元素在没有 grow 时也获得了一些主轴空间

例二

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%,问最后三者的实际长度分别为多少。



box1
box2
box3

解答:分别为 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 和第一列的并没有对齐,如何解决?



问题1:
问题2:
问题3:
问题 1 和问题 2 的 label 没对齐

如果能看懂 flexbox devtool 的话这个问题其实很好解决,我们把鼠标分别放到问题 1 和问题 2 的 label 上,可以看到:

这两者的实际主轴长度是相同的(紫色虚线区域大小相同),但是由于 flex 的主轴缩放导致了问题 2 label 缩短的长度更多(问题 2 的箭头更长)。因为箭头是朝内,所以说罪魁祸首就是 flex-shrink 的默认值 1。由于第二行容纳了四个元素,并且为了占满整行宽度,两个 input 的宽度都是 100%,导致第二行的两个 span 要承担更多的缩减。

解决方法很简单,把 label span 设置为 flex-shrink: 0 即可。

写在最后

flex 布局在日常开发中是经常用到的,但是一般都只是拿来调整对齐或者自适应,对 flex 子项的属性了解还是不够深入的。借这个机会深入了解一下,如果感兴趣的话可以看一下下面参考里加粗的两篇 MDN 文章,相信会加深你对 flex 的了解。

参考

  • What's New In DevTools (Chrome 90) - Chrome Developers
  • Flex 布局示例 (vgee.cn)
  • Flex 布局教程:语法篇 - 阮一峰的网络日志 (ruanyifeng.com)
  • flex 布局的基本概念 - CSS(层叠样式表) | MDN (mozilla.org)
  • 控制 flex子元素在主轴上的比例 - CSS(层叠样式表) | MDN (mozilla.org)
  • https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-basis

你可能感兴趣的:(从 chrome flexbox devtool 探究 flex 的主轴元素缩放)