美文网首页
从 chrome flexbox devtool 探究 flex

从 chrome flexbox devtool 探究 flex

作者: HoPGoldy | 来源:发表于2021-06-03 15:06 被阅读0次

    前言

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

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

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

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

    这种代表着 主轴 flex 子项的缩放效果,众所周知,flex 元素上可以通过 flex-growflex-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 值,可以看到,这个框的尺寸在大多数情况下是和元素本身的尺寸(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%,问最后三者的实际长度分别为多少。

    <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>
    
    问题 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 的了解。

    参考

    相关文章

      网友评论

          本文标题:从 chrome flexbox devtool 探究 flex

          本文链接:https://www.haomeiwen.com/subject/zlhosltx.html