Block Elements and inline elements(块元素和行内元素)
在进入正题之前,我们先来简单总结一下传统的block元素和inline元素。
- HTML中的block元素显示在页面上时总会另起一行,并占满它的父元素的整个宽度;block元素的宽高可以通过width和height来设置;
- inline元素正好相反,它们不会另起一行,只会占展示内容所需的宽度;width和height对inline元素不起作用;
- 另外,还有一种处于block元素和inline元素之间的inline-block元素,这种元素和inline元素一样,不会新起一行,但是可以通过width和height来设置它的宽高;
HTML元素都有其默认显示方式,如<p>元素默认为block元素,但我们还可以通过设置 display: block; display: inline; display: inline-block; 来改变元素的显示方式;
Flex-box模型
Flex-box模型让我们可以使用 display: flex; 把一个元素变成flex容器,不管这个元素原来是block元素还是inline元素,当它变成flex容器之后,它的行为会类似block元素,会占满父元素的宽度,也可以通过width和height来设置它的宽高;
让我们通过一个例子来说明,如下面的HTML代码所示,我们用一个<span>元素把三个<div>元素包裹起来,一般来说我们不会这么做,这里只是为了说明不管是block元素还是inline元素,只要对它设置了 display: flex; 它就会变成一个类似block元素的容器。
<span class="container"> <div class="item">item1</div> <div class="item">item2</div> <div class="item">item3</div> </span>
默认情况下它是这样子显示的(为了方便展示我给<span>和<div>分别添加了2px的边框):
当我们给<span>元素加上 display: flex; 之后:
<span>元素变成了一个flex容器,行为类似block元素,而且宽度和高度也可以设置;但奇怪的是,在<span>元素中的三个<div>元素按照规则应该是从上往下叠放,并且每一个都要占满父元素<span>宽度的,但它们反而是并排显示的。这是因为当一个元素变成flex容器之后,它的所有直接子元素都会变成flex item(类似inline-block),默认只会占内容展示所需要的宽度,而且不会另起一行;
让我们给<span>元素设置一下宽高 width: 300px; height: 150px; :
有没有发现现在三个flex item的高度跟父元素的高度是一样,这是因为 align-items 的默认值是 stretch;
当一个元素变成flex容器之后,它的所有直接子元素(flex item)就拥有了三个神奇的属性 flex-grow, flex-shrink, flex-basis,这三个属性是用来设置一个 flex item 要占多少宽度的;默认情况下他们的值分别是:
- flex-grow: 0;
- flex-shrink: 1;
- flex-basis: auto;
flex-basis
flex-basis 相当于 flex item 的 width, 当为一个flex item设置了 flex-basis: auto; 时,如果同时为这个flex item 设置了width属性,那么这个flex item的宽度就等于width属性的值,如果没有设置width属性,那么这个flex item只会占展示内容所需的宽度;让我们给第一个flex item设置 width: 100px;
现在第一个flex item的宽度变成了100px,但这不是一个好的做法,最好还是为一个flex item直接设置flex-basis而不是width属性;
如果我们给flex-basis指定一个除了auto以外的值,那么flex item的width属性的值就会被忽略掉;当我们同时给例子中的第一个flex item设置 width: 100px; flex-baisi: 50px;
现在第一个flex item的宽度变成了50px,它的width属性被忽略了;
flex-basis还接受其他的CSS单位,例如我们可以给每个flex item设置 flex-basis: 30%;
现在每个flex item的宽度都是父元素宽度的30%,那具体是多少呢?
之前我们设置了父元素的width等于300px,而且因为我们没有设置 box-sizing: border-box; ,所以这个flex容器的内宽度(不包括padding和border)就是300px,那每个flex item的内宽度就是 300px * 30% = 90px ,但因为每个flex item都有一个2px宽的边框,所以每个flex item的总宽度(内宽度+边框)是 90px + 2px * 2 = 94px ;
但如果我们给flex容器设置了 box-sizing: border-box; width: 300px; ,现在这个flex容器的总宽度是300px,但它也有一个2px宽的边框,所以它的内宽度变成是 300px - 2px * 2 = 296px ,这时每个flex item的内宽度就变成了 296px * 30% = 88.8px ,总宽度则还要加上两边的边框 88.8px + 2px * 2 = 92.8px ;
现在我们去掉flex容器的 box-sizing: border-box; 设置,它的内宽度又变回了300px;我们再给每个flex item设置 box-sizing: border-box; padding: 10px; ,现在每个flex item的总宽度(内宽度+padding+border)变成了 300px * 30% = 90px ;我们再给第一个flex item两边加上margin, margin: 0 10px; 可以看到这并不会影响它的宽度;
flex-shrink
现在把第一个flex item的 margin: 0 10px; 删掉:
flex容器的width还是300px,每个flex item的总宽度是 300px * 30% = 90px ,三个flex item加起来一共占了 90px * 3 = 270px ,小于flex容器的宽度;如果我们给每个flex item设置 flex-basis: 150px; 总宽度会超过flex容器的宽度,那会不会发生内容溢出的情况?
其实并不会,因为我们上面提到过,每个flex item都会默认设置 flex-shrink: 1; ,这个属性的作用就是当flex item宽度的总和大于flex容器的宽度时,适当地缩小flex item;
假如我们让每个flex item的宽度等于200px,三个加起来就是600px,然而flex容器只有300px宽,那么多出来300px就要被砍掉啦,因为每个flex item的flex-shrink都默认为1,所以它们会相应地缩小: 300px / 3 = 100px ,那么每个flex item缩小后的宽度就变成了 200px - 100px = 100px ;
flex-shrink还可以接收大于1的值,现在我们把第一个flex item设置为 flex-shrink: 2;
多出来的那300px会被分成 2 + 1 + 1 = 4 份,每一份是 300px / 4 = 75px ,第一个flex item会相应缩小两份变成 200px - 75px * 2 = 50px ,其他两个会分别缩小一份变成 200px - 75px = 125px ;
但flex-shrink不仅仅会根据多出来的那部分宽度缩小flex item的宽度,它还会根据flex item的flex-baisi的值;上面的例子中每个flex item的宽度都一样,现在我们把第一个flex item的设置为 flex-basis: 100px; flex-shrink: 1; , 并对第二和第三个flex item设置 flex-basis: 300px; flex-shrink: 2; ,现在三个flex item的总宽度是 100px + 300px * 2 = 700px ,而flex容器的宽度是300px,所以多出来的宽度是400px;
首先找出最小的宽度,在这个例子里是100px,然后用这个宽度去除每个flex item的宽度,得到:
- 100px / 100px = 1;
- 300px / 100px = 3;
- 300px / 100px = 3;
然后用得到的比例分别乘以它们的flex-shrink的值:
- 1 * 1 = 1;
- 3 * 2 = 6;
- 3 * 2 = 6;
得到的值加起来就是 1 + 6 + 6 = 13;
接下来就是计算每一个flex item应该缩小多少了,在这个例子中,多出来的宽度是400px,所以每个flex item分别约缩小:
- 400px * 1/13 = 30px
- 400px * 6/13 = 185px
- 400px * 6/13 = 185px
再用它们的flex-basis的值减去它们应该缩小的宽度就可以得到它们的实际宽度:
- 100px - 30px = 70px
- 300px - 185px = 115px
- 300px - 185px = 115px
把这些加起来 70px + 115px + 115px = 300px 正好是flex容器的宽度;
flex-grow
现在我们给每个flex item设置 flex-basis: 50px; box-sizing: boreder-box; ,flex容器的宽度依然是300px:
可以看到flex容器还有剩余的空间,但是flex item不会把它填满,因为它们的flex-grow默认值为0;如果我们把flex-grow设置为大于0的值,那么flex item就会相应地扩大以填满flex容器;现在我们给每个flex item设置 flex-grow: 1; 它们就会把flex容器剩余的空间填满:
现在每个flex item的宽度都是100px,我们来看一下这是怎么算出来的:
- 首先flex容器的width是300px,三个flex item加起来的宽度是 50px * 3 = 150px ,所以剩余的空间是 300px - 150px = 150px ;
- 我们有三个flex item,而且每一个的flex-grow的值都是1,所以剩余空间会被分成 1 + 1 + 1 = 3 份, 每一份是 150px / 3 = 50px ;
- 每一个flex item会增大50px宽,最后变成 50px + 50px = 100px ;
现在我们给第一个flex item设置 flex-grow: 3; 其他两个还是 flex-grow: 1; ,这个意思并不是说第一个flex item会是其他flex item的3倍:
现在第一个flex item的宽度变成了140px,其他两个则分别是80px,我们来看看是怎么算的吧:
首先flex容器的剩余空间还是150px,但这次不是被分成3份了,而是会分成 3 + 1 + 1 = 5 份(每个flex item的flex-grow的值相加),每一份是30px;
所以三个flex item分别会增加:
- 3 * 30px = 90px
- 1 * 30px = 30px
- 1 * 30px = 30px
最终的宽度分别是:
- 50px + 90px = 140px
- 50px + 30px = 80px
- 50px + 30px = 80px