flex:1 的所引发的思考
自从flex被加入css并被主流浏览器支持以来,我想我们已经感受到了这一属性的巨大魔力,并且对当初提出这一属性的某位尚未考究叫啥的大佬感恩戴德,让我们这些小前端在css控制布局时可以做到这样的美妙,而我这里所要探究的也是其中的一小部分,即flex:1这个属性,或者说这种写法。
这个写法对于我们常见的一些布局有着巨大的帮助,比如左侧边栏定宽右边自适应、顶和底定高中间自适应等,不需要再像以前用一些奇奇怪怪的绕来绕去的写法来表述。但是我们,真的了解这个flex:1吗?
本文比较长,想直接看结论的可以直接拉到底下看总结。
1、flex: 1的含义
翻看MDN手册我们就可以知道,flex:1实际是一个简写,等价于flex-grow: 1; flex-shrink: 1; flex-basis: 0%; 它们分别代表了所定义flex盒子的拉伸因子、收缩规则、基础宽度。(目前先只讨论flex主轴默认的情况,更换主轴则下文的“宽度”均会变为“高度”)
为了方便接下来解释的清晰,我们不妨就来做个通用于全文的比喻:一位信仰flex教的老父亲有一笔有限的财富(width),它有若干个孩子,这些孩子各有各的本里,有的能文有的能武(拥有不同的css属性,如flex、width等),其中子元素的width就是他们提出的想要的财产多少,也就是他们的“欲望”,有的有孩子有的没孩子(有没有更下一级的孙子元素),孙子们也都各有本事。然后父亲将根据W3C教义将他的财产分配给他的孩子,下文我们所要探究的就是在不同的情况下他的财产将如何被划分
2、flex-grow
flex-grow,我们平时设置flex:1, flex: 2的区别其实拆解开来就是反映在flex-grow上,这也是本文重点探讨的对象。我的理解,它是“剩余财产分配比例”,注意下文我们先探讨设置了flex:n以后的情况。
先谈“分配比例”:就是如果多个flex元素的宽度总和超过父元素宽度(未设置flex-warp),那么flex的机制就会让它们进行压缩宽度重新分配宽度,flex-grow实际就是这里的分配比例,flex-grow:2的元素宽度会是值为1的元素的2倍。
再谈“剩余财产”:老父亲永远会优先考虑有孩子们的“欲望”,根据孩子想要的width分配给他们财产,而如果有分配剩下的,那么再根据这个“分配比例”分给剩下的孩子;如果可分配的财产不够分,那这些设置了flex-grow的孩子一个px都拿不到,而设置了width的元素将按其“欲望”大小比例分配。
值得注意的是,如果一个元素已经设置了flex:n,那么就算它也设置了width,那么这个width也会被无视,让这个元素称为“分配剩余待选项”,注:原因将在下面flex-basis中详细讲述
这里有一个知识点要注意:flex:1本质上是获取父元素宽度的百分比,是一种“继承”,与height:100%不一定能正确获得父元素高度一样,如果父元素没有自己的固定宽度也没有继承来的宽度,那么很可能你即便设置了flex:1也无法让子元素宽度自适应。
上述情况还比较好理解,但是实际情况会有另一个情况,那就子元素又有了子元素,于是这时候孙子的出现让这个世界都不一样了。
先看示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0; padding: 0;
}
.father {
height: 100px;
width: 500px;
background-color: salmon;
display: flex;
}
.first {
width: 200px;
background-color: blue;
}
.second {
background-color: bisque;
flex: 1;
width: 100px;
}
.grandSun {
width: 50px;
}
.third {
background-color: aqua;
flex: 1;
width: 100px;
}
.grandSun2 {
width: 9999999px;
}
.forth {
width: 200px;
background-color: aquamarine;
}
.forthSun {
width: 200px;
}
</style>
</head>
<body>
<div class="father">
<div class="first">150px被挤压</div>
<div class="second">
<div class="grandSun">100px满足孙子</div>
</div>
<div class="third">
<div class="grandSun2">200px受限于其父</div>
</div>
<div class="forth">
<div class="forthSun">200px</div>
</div>
</div>
</body>
</html>

我们可以看到,老父亲对于孙子的“欲望”优先级是高于儿子的“欲望”,由于总需求超过了老父亲的财产,大儿子本来要求的200px被挤压了50px,二儿子得到了他的孩子所需求的100px,而小儿子right由于也有一个想要200px的孩子于是老父亲保留了right的宽度。这里值得一提的是,这里三儿子的孩子,需求一个无穷大的宽度,但是实际只拿了老父亲的200px,因为三儿子设置了他自己的一个width,导致无论他孙子要求多大的东西,最后从老父亲手里拿到的宽度总是无法超过200px。
那么假如,三儿子没有设置width呢?那这个时候三儿子的孩子欲望就会无限膨胀,超过了整个家族。

这里就可以提另一个知识点了:有时定义了flex:1的元素A宽度会被内部子元素撑开,那么可以给A设置一个width:1px来让其内部的宽度优先级失效。这样设置之后,老父亲就基本不会再管孙子的需求,仅仅考虑它孩子们的需求。而width:1px本身也不会影响到设置了flex:1的元素获取width的优先级。
更多层的子元素相互作用影响最上层的元素夺取宽度的情况我并没有测试,因为我认为这种影响应该要控制在3层以内,太长的影响链会导致更复杂的书写难度和BUG产生率,所以我认为应当在适当的地方写上width:1px来终止这种影响。
3、flex-shrink
flex-shrinkMDN上的翻译为“收缩规则”,其实这个翻译是看的我一头雾水根本不知道在说啥,我的个人理解用白话文说就是“损失容忍度”。我这里翻到菜鸟教程里的解释我认为更容易理解:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
#content {
display: flex;
width: 500px;
}
#content div {
flex-basis: 120px;
border: 3px solid rgba(0,0,0,.2);
}
.box {
flex-shrink: 1;
}
.box1 {
flex-shrink: 2;
}
</style>
</head>
<body>
<p>div 总宽度为 500px, flex-basic 为 120px。</p>
<p>A, B, C 设置 flex-shrink:1。 D , E 设置为 flex-shrink:2</p>
<p>D , E 宽度与 A, B, C 不同</p>
<div id="content">
<div class="box" style="background-color:red;">A</div>
<div class="box" style="background-color:lightblue;">B</div>
<div class="box" style="background-color:yellow;">C</div>
<div class="box1" style="background-color:brown;">D</div>
<div class="box1" style="background-color:lightgreen;">E</div>
</div>
</body>
</html>
代码结果是这样的:

而反过来一想,假如有个孩子的“损失容忍度”为0,那是不是他的欲望就能确保得到100%满足了?其实是的,如果一个元素设置flex-shink: 0,可以保证其宽度不被挤压。极端情况它想要的width超过了父元素所拥有的的width,那么父元素将把所有宽度全部给它。父亲不让孩子们打起来是第一优先级
4、flex-basis
flex-basis其实定义理解起来是就比较容易的,它表示在“flex 元素在主轴方向上的初始大小”,它的值也有很多种写法,px/em/%/auto/fill……(详情自查MDN文档)。其实简单来说,没有其它因素干扰的话,你设置flex-basis:80px,那么这个元素就是80px宽了。然鹅!这个世界上哪有这么简单的事情嘛。
这个属性通常在flex:1时实际为0%,而这个0%就是我们说flex:1是争取“剩余财产”的原因,也是这个0%导致了元素设置width无效,因为它实际是覆盖了width这个“欲望”,只要设置了flex-basis就不认width了,所以实际上如果你单独设置flex-shrink: 1; flex-grow: 1;的话width依旧是有效的。
那如果它不是0%,而是想width一样是个具体值,那会是什么样呢?我们看看代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0; padding: 0;
}
.father {
height: 100px;
width: 550px;
background-color: salmon;
display: flex;
border: 1px solid red;
}
.first {
width: 200px;
background-color: blue;
}
.second {
background-color: bisque;
flex: 1;
flex-basis: 100px;
}
.third {
background-color: aqua;
flex: 1;
}
.forth {
flex: 1;
flex-basis: 100px;
background-color: aquamarine;
}
</style>
</head>
<body>
<div class="father">
<div class="first">200px</div>
<div class="second">150px</div>
<div class="third">50px</div>
<div class="forth">150px</div>
</div>
</body>
</html>

我们这里看到,大儿子要求200px得到满足,二儿子、四儿子要求100px,实际得到了150px,而三儿子仅仅获得50px,什么鬼?
实际我们需要这样分解,每个元素对宽度的需求有2个部分:1、欲望,即width或flex-basis;2、将剩余按照flex-grow的分配比例。
首先,老父亲第一步先满足所有明面上的“欲望”,分配width/flex-basis,如果不足则按欲望比例分然后就完事,如果还有剩余的那第二步再根据flex-grow去分配,也就是说如果一个元素即有“欲望”又有“剩余分配比例”,那么他很可能在这两步内都获得了财产,最后获得量很可能是多余其“欲望”的。
上文代码中二儿子和四儿子的150px,100px是他们flex-basis获取到的,然后老父亲分配完大家的“欲望”后把多余的宽度又根据大家的“剩余分配比例”重新分配了一遍,于是二儿子四儿子又得到了50px。
5、混合之后
如果上述三个属性混合出现会有什么事情呢?这又是个头大的问题了,上文中我们已经知道,如果财产能满足所有人欲望,那“损失容忍度”flex-shrink就不会派上用处,那情况就比较简单,而我们现在就要探讨下财产不够分的情况。看代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0; padding: 0;
}
.father {
height: 100px;
width: 300px;
background-color: salmon;
display: flex;
border: 1px solid red;
}
.first {
width: 100px;
background-color: blue;
}
.second {
background-color: bisque;
flex: 1;
flex-basis: 100px;
}
.third {
background-color: aqua;
flex: 1;
}
.forth {
flex: 1;
flex-basis: 100px;
flex-shrink: 2;
background-color: aquamarine;
}
</style>
</head>
<body>
<div class="father">
<div class="first">1</div>
<div class="second">2</div>
<div class="third"></div>
<div class="forth">4</div>
</div>
</body>
</html>

这里我们父元素只有300px,而1,2,4儿子都以不同的姿势表达了想要100px,只有3儿子非常“仁爱”表示挑剩下的就够了,于是1,2儿子得到了75px,4儿子得到了50px,而3儿子由于“谦让”一个px都没分到。
这里我们可以很容易发现,仅设置width: 100px和设置flex-shrink: 1; flex-basis: 100px其实对于父亲来说是等价的,都认为他们想要100px。然后4儿子的“损失容忍度”为2,是大儿子、二儿子的两倍,于是四儿子承受了50px的“不满足”,大儿子、二儿子承受了“25px”的损失,三儿子反正没要财产就不分了。
总结
这位老父亲根据W3C教义,首先会看孩子们用width/flex-basis表达出的“欲望”总和是不是超过了自己的财产总量。假如财产不够分,就需要确保他的孩子们不会打起来,那么flex-shrink这一“损失容忍度”会被优先照顾,然后再根据各自的“欲望”按比例分配财产;假如财产够分,那么“损失容忍度”会被无视,财产将先满足孩子们的“欲望”,然后剩下的多出来的财产将按照flex-grow“剩余财产分配比例”来分配,所有有此值的孩子不论之前是否已经分到过财产,都会重新再分配一次。另外孙子的欲望可以被父亲的width给束缚,而假如不束缚的话可能爷爷会因为偏爱孙子为了满足孙子的欲望可能会把整个家族都送给孙子。
重点知识点归纳
flex:1本质上是继承,如果父元素不知道自己有多宽,也就无法给子元素分配flex: 1; width: 1px;可以阻止子元素影响当前元素对于width获取的优先级flex-shrink: 0可以保证争取到自己所需的宽度,“0容忍,不服就干”- 宽度分配先尽量满足所有
width/flex-basis,然后把剩余宽度交给flex-grow分配
后记
这算是我这位萌新前端第一篇认认真真写的文章了,本以为几百字就能解释清楚,结果发现有些东西自己写着写着也糊涂了,反复查资料又自己实验后才知道自己了解的还是不够,写这篇文章也算是个人技能与表达能力的一次锻炼吧。
当然,如果文中有写的不对的地方,或者有写的不好的地方,也欢迎大佬们指出!
先立个flag,后面打算再写一写element-ui中el-scrollbar的用法,这东西可真是BUG又多又难用,难怪官方给了调用接口却不写文档。我想整理下这东西到底怎么用比较好,又有些什么BUG、该怎么处理