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

前言

自从 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 的了解。

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 网页布局(layout)是CSS的一个重点,传统的方式是基于盒子模型,依赖display、position、flo...
    JunChow520阅读 1,091评论 0 0
  • 原文地址:https://segmentfault.com/q/1010000004080910/a-102000...
    小豆soybean阅读 20,658评论 2 6
  • 1. flex布局 参照:阮一峰的文章 2. flex:1的理解 2.1 概念 flex:是 flex-grow、...
    锦锦_jane阅读 6,077评论 0 9
  • H5移动端知识点总结 阅读目录 移动开发基本知识点 calc基本用法 box-sizing的理解及使用 理解dis...
    Mx勇阅读 4,389评论 0 26
  • HTML 5 HTML5概述 因特网上的信息是以网页的形式展示给用户的,因此网页是网络信息传递的载体。网页文件是用...
    阿啊阿吖丁阅读 3,828评论 0 0