如何理解 flex-grow, flex-shrink, and flex-basis

779 阅读8分钟

当你对一个元素应用CSS样式的时候,其实在幕后有许多事情发生,举个例子,比如我们有如下的HTML代码:

<div class="parent">
   <div class="child">child</div>
   <div class="child">child</div>
   <div class="child">child</div>
</div>

并且当我们应用如下CSS样式时:

.parent {
	display: flex;
}

准确来说,当我们编写上述代码时,这一行样式并不是唯一应用到的样式,事实上,很多CSS样式或属性会被应用到 child 类的元素上,就仿佛我们自己写出这些样式一样,下面就是上述代码中默认应用到child类的元素上的样式内容

.child {
	flex: 0 1 auto;/* Default flex value */
}

这看起来非常奇怪,为什么我们没有写上述的样式,但它们还是被应用到了child类元素上。实际上原因很简单,因为有些属性本身就有默认值,并且这些默认值一般会被人为更改掉,当我们写CSS时,如果我们不知道这些属性的默认值,甚至不知道这些属性正在被应用,我们的CSS布局将会变得混乱且难以管理。

上面css代码中的 flex 属性设置方式就是CSS中常见的属性简写方式,实际上它一次性设置了三个属性,翻译成普通写法如下:

.child{
	flex-grow: 0;
	flex-shrink: 1;
	flex-basis: auto;
}

因此,一个简写的属性绑定了一堆不同的属性,使得一次性编写多个CSS属性变得方便快捷,同样使用简写的方法的还有属性 background

body {
	background: url(text.jpg) top center no-repeat fixed padding-box content-box red;
}

大部分时间里,我尝试避免使用简写来设置 CSS 属性,因为对于我来说理解一长串属性设置太过复杂,所以我更倾向于写单行设置一个属性的版本,虽然写的内容较多,但可读性强,但是在flexbox中设置 flex item 的属性时更推荐使用简写,听起来有点奇怪,因为 flex 属性做了很多工作,并且其中每个子属性均与其他子属性存在交互.


另外默认样式有个好处,在90%以上的时间里,使用者不需要知道这些flex属性做了什么,举个例子,当我们使用flexbox来进行布局时,我一般会写如下的样式:

.parent {
	display: flex;
	justfiy-content: space-between;
}

我不需要关心子元素上应用了什么样式,在上述例子中,我们将子元素并排对齐,然后将它们彼此距离相等的隔开,两端的元素紧贴着父元素的左右两边; 仅仅两行 CSS 就有如此巨大的效果,这是 flex 布局和其继承的样式最巧妙的地方和哲学——如果你想在90%的时间里做同样的事情,你就不需要理解事情背后所有的复杂性。它非常的聪明,因为所有的复杂性都隐藏在视线之外。

但是如果我们想了解 flexbox 是如何工作的,比如 flex-grow、flex-shrink、flex-basis这些属性是如何工作的?我们可以利用它做什么事情?

让我们从一个简单的概述开始,先回到应用在 child 元素上的 flex 默认属性上:

.child {
	flex: 0 1 auto;/* Default flex value */
}

这些默认的样式告诉 child 元素该如何的伸展扩大,但是每当我看到这些简写属性被使用或被重写,我发现用如下这种方式写对思考这些简写属性的作用很有帮助:

.child {
	flex: [flex-grow] [flex-shrink] [flex-basis]
}

/* 或者 */

.child {
	flex: [max] [min] [ideal size]
}

第一个属性是 flow-grow 并且被设置为0,因为默认情况下,我们不想让我们的元素放大(多数情况),相反,我们想让每一个元素的大小都取决于元素其中内容的大小,这里有一个例子:

<div class="parent">
	<div class="child one" contenteditable>
		child
	</div>
	<div class="child two" contenteditable>
		This is some longer conten
	</div>
	<div class="child three" contenteditable>
		child
	</div>
</div>
.parent {
	background-color: #001f3f;
	display: flex;
	padding: 20px;
}

.child {
	background-color: #0074d9;
	flex-grow: 0;
	color: white;
	/* flex: 0 1 auto; 默认不写的情况下各属性值,这里flex-grow默认值为0 */
}

随着 child two 类元素内容增多,其所占宽度就越大,这就是 flexbox item 的默认行为:flex-grow值设为0,则元素基于它内部的内容而增长。

但是当我们更改 flex-grow 属性的默认值从0更改为1,就像如下情况:

<div class="parent">
  <div class="child one">child</div>
  <div class="child two">child</div>
  <div class="child three">child</div>
</div>
.parent {
  background-color: #001f3f;
  display: flex;
  padding: 20px;
}

.child {
  background-color: #0074d9;
  padding: 20px;
  color: white;
  flex-grow: 1;
}

上面的例子中设置了 flex-grow 的值为1,并且忽略了其他的属性,使其保持默认值,实际上这里相当于写成了

.child {
  background-color: #0074d9;
  padding: 20px;
  color: white;
  flex: 1 1 auto;
}

当我们想让其中第三个 child 比其他两个元素更大时,我们只需要写成如下样式:

.three {
	flex: 3 1 auto;
	background-color: #3d9970;
}
/* 或者是下面的格式 */
.three {
	flex-grow3;
	background-color: #3d9970;
}

在上面的例子中第三个 child 元素尝试扩大成另外两个元素的三倍大小,前面两个元素会按各自内容比例占据剩余的空间。

值得注意的是,或者说会让人感到困惑的是,当第二个元素的内容足够多时,它可以占据更大的地盘,哪怕第三个元素的 flex-grow 属性设置为3,就像如下的例子:

<div class="parent">
  <div class="child child-one">child</div>
  <div class="child child-two">Now this is a lot of content that goes here and to be honest we should be wary of changes.</div>
  <div class="child child-three">child</div>
</div>
.parent {
  background-color: #001f3f;
  display: flex;
  padding: 20px;
}

.child {
  background-color: #0074d9;
  padding: 20px;
  flex-grow: 1;
  color: white;
}

.child-three {
  flex-grow: 3;
  background-color: #3d9970;
}

可以看到第二列占据了如此多的空间,由此可见,flex item 的内容大小对于属性 flex-grow flex-shrink flex-basis 有非常重大的影响


同理我们在介绍一下 flex-shrink 以及 flex-basis

flex-shrink 属性告诉浏览器一个元素的最小的大小应该是多少,该属性默认值为1,意思是“无论在什么时候,元素始终占据相同的空间“,然而当我们设置该值为0时,例如:

.child {
  flex: 0 0 auto;
}

此时是告诉该元素现在不要收缩,保持原本的尺寸大小,我们再来看下第三个属性 flex-basis

flex-basis 是 flex属性简写中第三个值,它是我们告诉元素要保持一个理想的大小的方法,默认情况下,它的值为 auto ,这种情况是指元素的理想大小取决于元素内容的大小。

为了让所有元素占满父元素的空间,我们可以设置子元素的 width 属性为100%,或者我们可以设置子元素的 flex-basis 属性为100%,或者我们可以设置 flex-grow 属性为1。

讲到这里,仔细考虑下为什么我们会推荐使用简写来设置 flex 的属性值而不是分开来设置每一个属性的值,当然是由于这些简写值对彼此都有影响。

ok,我们举几个常用例子来理解上句话的含义:

<div class="parent">
  <div class="child child-one">child</div>
  <div class="child child-two">child</div>
  <div class="child child-three">child</div>
</div>
.parent {
  background-color: #001f3f;
  display: flex;
  padding: 20px;
}

.child {
  background-color: #0074d9;
  padding: 20px;
  color: white;
}

.child-three {
  flex: 0 1 1000px;
  background: #ff851b;
}

有人可能会问,第三个元素明显不够1000px啊,那是因为 flex-basis 只是一个建议值,并且在应用该样式的同时,我们同时还应用了 flex-shrink:1; 这条 CSS 告诉浏览器该元素要收缩到和其他元素一样的大小。

这种情况下,如果第二个元素里内容增加变多,依然会对第三个元素的大小造成影响

如果我们想防止第三个元素被其他元素影响而收缩自己的大小,我们可以设置其CSS为如下样式:

.child-three {
  flex: 0 0 1000px;
}

记住,flex-shrink 是简写属性的第二个,将其设置为0,则是告诉浏览器,该元素不会收缩,所以该元素甚至有可能突破父元素的大小,因为它不肯收缩至小于1000px的大小类似下图

如果我们在增加一条CSS,flex-wrap 属性给 parent 元素,一切都变了:

.parent {
  display: flex;
  flex-wrap: wrap;
}

.child-three {
  flex: 0 0 1000px;
}

增加之后,上图就会变成如下效果:

为什么会出现这种情况?

是因为在默认情况下 flex itemss将会尝试都放进一行之中,但是当增加了 flex-wrap: wrap; 之后就会完全忽略这种限制,现在当 flex items 无法全部塞进一行里,就会另起一行。


这些就是 flex 属性之间相互碰撞影响的方式,理解这些背后的原理是非常有价值的,这些属性值中的每一个都互相影响着彼此,如果你不明白其中一个是如何工作的,那么就不会明白他们全部是如何工作的,这也是我在深入研究该属性值前一直困惑的问题。


总结一下:

  • 尝试使用 flex 的简写方式
  • 记住 [max] [min] [ideal size]
  • 记住,元素内容的大小也会影响 flex 多个属性值的协同工作