CSS 弹性布局 (display:Flex) 基础篇

406 阅读18分钟

前言

最近几个月由于本职工作比较清闲,所以就想着协助公司同事完成一些前端的基础开发工作,同时也是想学习扩展一下前端的技术栈,在这几个月里也前后参与了多个前端项目,有Vue的管理后台,也有html + bootstrap的官网,最终还是觉得收获满满,感谢前端同事的不嫌弃和耐心指导。本文是我对弹性布局(display: flex)的一个学习笔记,也是我对自己最近几个月学习前端的一个小总结。

基础概念

弹性布局是由弹性容器和一个或者多个弹性子元素组成的,而且我们要使用display: flex或者display: inline-flex来定义弹性容器。有了弹性容器之后,容器中的所有子元素自动会成为容器成员,也就是弹性子元素。

在学习弹性布局的时候,我们要明白弹性容器中是有2根轴线:主轴交叉轴,这两根轴线对我们学习弹性布局至关重要。可结合下图来了解这两根轴线。

为了便于加深记忆,我实现了这么一个图片,如下图:

图1.png

基础属性

容器属性

弹性容器上常用的属性有以下几种:

  • flex-direction
  • flex-wrap
  • justify-content
  • align-items
  • align-content
  • flex-flow

flex-direction

该属性是用于设置主轴方向的,对应4个属性值:

  • row
  • row-reverse
  • column
  • column-reverse

该属性的默认值row,也就是表示容器中的主轴为水平方向,从左至右。

为了理解该属性的意义,我又做了下面的效果图。


设置容器的 style 为 flex-direction: row:

flex-direction-row.png

由上图可以看出来,当设置该属性为row时,主轴为水平方向,容器子元素从左至右排列;


设置容器的 style 为 flex-direction: row-reverse:

flex-direction-row-re.png

由上图可以看出来,当设置该属性为row-reverse时,主轴为水平方向,容器子元素从右至左排列;


设置容器的 style 为 flex-direction: column:

flex-direction-column.png

由上图可以看出来,当设置该属性为column时,主轴为竖直方向,容器子元素从上至下排列;


设置容器的 style 为 flex-direction: column-reverse:

flex-direction-column-re.png

由上图可以看出来,当设置该属性为column-reverse时,主轴为竖直方向,容器子元素从下至上排列;


flex-wrap

该属性用于设置弹性容器中子元素超出容器后是否换行,对应了3个属性值:

  • nowrap
  • wrap
  • wrap-reverse

该属性的默认值nowrap,也就是说当子元素超出容器后通过一定的比例公式来缩放子元素尺寸来达到实际布局效果目的。

废话不说了,直接上图(关于这个属性的图简单注释一下:容器的 max-width: 600px, 子元素的width: 50px)。


设置容器的 style 为 flex-wrap: nowrap或者不设置容器的flex-wrap属性:

flex-nowrap.jpg

由上图我们可以看出来,当设置该属性为nowrap,或者不设置flex-wrap属性时,容器中的子元素超出容器时,容器会根据一定比例公式来使所有子元素按照主轴方向来铺满整个容器空间


设置容器的style为flex-wrap: wrap:

1.jpg

由上图我们可以看出来,当设置该属性为wrap时,当子元素按照主轴方向铺满整个容器空间后,剩余子元素会按照交叉轴的方向自动换行


设置容器的style为flex-wrap: wrap-reverse:

flex-wrap-2.jpg

由上图我们可以看出来,当设置该属性为wrap时,当子元素超出容器空间时,子元素会从交叉轴的反方向(即从下到上)开始,按照主轴的方向来铺满容器空间后,剩余子元素按照交叉轴的反方向自动换行


justify-content

该属性用于设置弹性容器中子元素在主轴上的对齐方式,对应了6个属性值:

  • flex-start
  • flex-end
  • center
  • space-between
  • space-around
  • space-evenly

该属性的默认值为flex-start,也就是说当设置该属性为默认值时,容器中的子元素按照主轴的方向排列。


设置容器的 style 为 justify-content: flex-start或者不设置容器的justify-content属性:

justify-content-1.jpg

由上图我们可以看出来,当设置该属性为flex-start,或者不设置 justify-content 属性时,容器中的子元素按照主轴的方向排列,如果有空余空间则在主轴的尾端(由于图中的容器未设置主轴方向属性,所以默认主轴的方向为从左至右水平方向)。


设置容器的 style 为 justify-content: flex-end:

justify-content-2.jpg

上图中的容器未设置flex-direction属性,所以容器的主轴方向为水平从左至右,通过上图我们不难发现,当该属性的值为flex-end时,容器中的子元素会从主轴的尾端开始排列元素的最后一个,依次向主轴的反方向排列,如果有空余空间则在主轴的首部


设置容器的 style 为 justify-content: center:

justify-content-3.jpg

当该属性的值为center时,容器中的元素会在主轴方向居中排列,如果容器有剩余空间则会被平均分成2分,分别位于主轴方向的首尾


设置容器的 style 为 justify-content: space-between:

space-between.jpg

当该属性的值为space-between时,所有元素平均分布在主轴方向上,由于这里的主轴方向为水平至左向右,所以第一个元素与容器主轴首部左对齐,最后一个元素与容器尾部右对齐,剩余元素间距相等,如果剩余空间为负或者至右一个元素时,效果等同于将该属性设置为 flex-start


设置容器的 style 为 justify-content: space-around:

space-around.jpg

当该属性的值为space-around时,第一个元素距离主轴的首部的距离和最后一个元素距离主轴尾部的距离相同,其他元素距离左右元素的间隔都是相同的,如上图所示,容器宽度为600px,首尾的间距为 (容器宽度 - 元素宽度)/ 元素个数 / 2,也就是说首尾间距为20px,元素之间的间距为40px;如果剩余空间为负或者只有一个元素时,效果等同于将该属性设置为 center


设置容器的 style 为 justify-content: space-evenly:

space-3.jpg

当该属性的值为space-evenly时,所有元素之间间距相等,且与元素距离容器主轴首尾的间距相等。如上图所示,容器宽度为600px,间距为(容器宽度 - 元素宽度)/ (元素个数 + 1),也就是说上图中的所有间距均为30px;如果剩余空间为负或者只有一个元素时,效果等同于将该属性设置为 center


align-items

该属性用于设置弹性容器中子元素在容器的交叉轴当前行上的对其方式,对应了5个属性值:

  • stretch
  • center
  • flex-start
  • flex-end
  • baseline

该属性的默认值stretch ,当元素未设置高度或者高度设置为auto时,元素会被拉伸充满容器的当前交叉轴空间。


设置容器的style为align-items: stretch

image.png

上图中容器的交叉轴的方向是至上而下的竖直方向,且元素未设置高度,元素会被拉伸,并充满容器的交叉轴。


设置容器的style为align-items: center

image.png

当该属性值为center时,元素会与交叉轴的中心对齐,如果元素超出容器,会两个方向同时超出相同的长度,即如果交叉轴为竖直交叉轴时,元素超出容器,则会向上下两个方向同时超出。


设置容器的style为align-items: flex-start

image.png

当该属性值为flex-start时,元素会与交叉轴的起点对齐


设置容器的style为align-items: flex-end

image.png

当该属性值为flex-end时,元素会与交叉轴的终点对齐


设置容器的style为align-items: baseline

image.png

当该属性值为align-items: baseline时,元素的文字的基线对齐,即元素中的行内轴对齐。


align-content

该属性用于设置弹性容器中所有子元素在容器的交叉轴上的对其方式,对应了6个属性值:

  • stretch
  • center
  • flex-start
  • flex-end
  • space-between
  • space-around

该属性的默认值为 stretch,也就是说当元素不设置高度或者将高度设置为auto时,将会被拉伸填充满容器的交叉轴空间。

Tip:使用align-content时需注意:弹性容器设置了flex-wrap: wrap,且容器中不只是一条交叉轴线。


设置容器的style为height: 200px; align-content: stretch,且容器中子元素需要多行展示

image.png

上图中容器中元素被分成3行,所以时有3个交叉轴的,当该属性设置为stretch时,该容器会被按照3条交叉轴线进行均分,让这些未设置高度的元素填充整个交叉轴空间。也就是说容器高度为200px,设置这个属性值后,上图中的元素的高度都为66.66px。


设置容器的style为height: 200px; align-content: center,且容器中子元素需要多行展示

image.png

上图中第一行第二个黄色元素高度为所有元素中最大的,值为50px,第二、三排的元素高度均为40px,且该容器拥有3个交叉轴,当设置了该属性值为center时,这3个交叉轴所在空间会与交叉轴的中心点对齐,上下间距为35px。


设置容器的style为height: 200px; align-content: flex-start,且容器中子元素需要多行展示

image.png

当该属性值设置为flex-start时,整个交叉轴空间会与容器的交叉轴起点对齐


设置容器的style为height: 200px; align-content: flex-end,且容器中子元素需要多行展示

image.png

当该属性值设置为flex-start时,整个交叉轴空间会与容器的交叉轴终点对齐


设置容器的style为height: 200px; align-content: space-between,且容器中子元素需要多行展示

image.png

当该属性设置为space-between,第一条交叉轴线与容器的交叉轴上的起点对齐,最后一条交叉轴线与容器的交叉轴上的终点对齐,每个子交叉轴线上的空间之间的间距相同(即如图所示,第一行和第二行,第二行和第三行之间的间距是一致的);如果剩余空间为负数,该属性值的效果与讲该属性设置为flex-start的效果一致。


设置容器的style为height: 200px; align-content: space-around,且容器中子元素需要多行展示

image.png

当该属性设置为space-around时,每个轴线两侧的间距相同,如上图所示,共有3个轴线,容器高度为200px,第一行第二个黄色元素高度为所有元素中最大的,值为50px,第二、三排的元素高度均为40px,轴线两侧间距的计算是 (容器高度 - 元素的总高度)/ 轴线个数 * 2,所以轴线两侧的间距为11.66px,第一行距离顶部的高度为11.66px,最后一行距离底部的高度为11.66px,第一行距离第二行,第二行距离第三行的间距都是23.32px。


flex-flow

该属性是flex-directionflex-wrap两个属性的组合属性,默认值为row nowrap

元素属性

元素常用的属性有以下几种:

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex
  • algin-self

order

该属性用来定义元素在容器中的排列顺序默认值为0,值为 Number 类型,表示元素按照容器属性来进行依次排列。

image.png

在上图中,弹性容器中子元素未设置 Order 属性时,元素的排列顺序为:红元素、黄元素、绿元素;当红色子元素的 Order 属性设置为 1 时,排列顺序变成了:黄元素、绿元素、红元素,也就是说当不设置元素的 Order 时,元素会按照容器的排序规则进行排序,当设置了元素的 Order 时,数值越大,排列越靠后,当数值一样时,他会按照元素在容器中的顺序排列。

flex-grow

该属性用来定义当容器中有剩余空间时,元素的放大比例,默认值为 0,值为 Number 类型

下面这张图,只定义了一个弹性容器,容器宽度为600px以及三个子元素的宽度均固定为100px(后面将这一条件叫做基础条件):

image.png

下面一张图,在上图条件中新增了三个元素的flex-grow均为 0 :

image.png

结论一:flex-grow0时,元素不会放大,会按照主轴方向正常排列。

还是基于针对基础条件,将黄色元素的 flex-grow 设置成 1, 将绿色元素的 flex-grow 设置成 2

image.png

由上图我们发现,子元素充满了整个容器主轴空间,现在我们大概会看到黄色元素宽度是红色元素宽度的2倍,绿色元素宽度是红色元素宽度的3倍。(其实事实也是这样的,因为我设置的宽度比较巧,想知道为什么这么巧不?(bu)想知道,请往下看)。

flex-grow其实是把主轴空间的剩余部分进行分割,填补给对应需要放大的元素中,像图中总宽度为600px,三个元素的实际宽度和为300px(每个元素100px),这样容器空余空间还有300px,黄色的 flex-grow 是 1,绿色的 flex-grow 是 2,所以将剩余空间均分为3份(所有 flex-grow 的和),这样每份是100px,又因为黄色的初始宽度为100px,又放大了 1,所以黄色的宽度 100 + 100 = 200px,绿色也是同理,变成了宽度为 100 + 100 * 2 = 300px。

结论二: flex-grow属性是为了填充容器主轴的空余空间的放大,如果主轴的空余空间为负时设置该属性无效果。

flex-shrink

该属性用来定义当容器中没有有剩余空间时,元素的缩小比例,默认值为0,值为 Number 类型

现在定义一个弹性容器宽度为600px,容器中包含了3个宽度均为300px的子元素,效果如下:

image.png

然后我们修改上述元素的属性 flex-shrink的值, 红色元素设置为1,黄色元素设置为2, 绿色元素设置为3,效果如下:

image.png

现在红色元素的宽度为 250px,黄色元素的宽度为 200px,绿色元素的宽度为 150px,按照上述描述元素超出容器宽度为300px,红色元素缩小 1,黄色元素缩小 2,绿色元素缩小 3,300 / (1 + 2 + 3) = 50px,红色元素总长是300px,所以 300px - 50px = 250px, 三个元素的宽度应该是250px 200px 150px,如果你是这么想的,请继续往下看。

我们修改红色元素的flex-shrink为 2, 黄色元素的flex-shrink为 3,绿色元素的flex-shrink为 5,其他条件都不改变,得到以下效果:

image.png

根据上一张图,我们得出的结果应该是,超出部分为 300px, 300 / (2 + 3 + 5) = 30px,因为红色元素缩小 2,黄色元素缩小 3, 绿色元素缩小 5,所以三个元素的宽度应该是 240px 210px 150px,对么?其实三个元素的实际宽度也是 240px 210px 150px。你想的也许是对的,我们继续往下看。

这次我们狠一点修改,容器宽度仍不变为 600px,红色宽度设置为 200px 且 flex-shrink设置为 1,黄色元素宽度设置为 400px 且 flex-shrink设置为 2,绿色元素设置为400px 且 flex-shrink设置为 1,得到下面效果:

image.png

我们仍按照之前思考的那个公司去计算减少,超出为200px, 200 / (1 + 2 + 1)= 50px,红色元素的宽度为 200 - 50 = 150px,即三个元素的宽度分别为 150px 300px 350px,这么计算的对吧?(其实如果这么算下来,结果三个元素的宽度相加不等于容器的宽度了)😜,实际上三个元素的宽度为142.86px 171.42px 285.72px。

我们来看一下实际上的 flex-shrink的计算,它是有一个加权公式的。

当元素超出容器时,元素设置了flex-shrink时,用各个元素的宽度 ×\times flex-shrink的值,然后相加得到一个系数:

红色元素: 200 * 1 = 200

黄色元素: 400 * 2 = 800

绿色元素: 400 * 1 = 400

然后我们将三个元素的相加得到一个系数

200 + 800 + 400 = 1400

然后我们清算每个元素别缩小的部分的宽度了

红色元素超出部分的宽度: 200 / 1400 * 超出的部分的宽度(即400px) = 57.14px

黄色元素超出部分的宽度: 800 / 1400 * 超出的部分的宽度(即400px) = 228.57px

绿色元素超出部分的宽度: 400 / 1400 * 超出的部分的宽度(即400px) = 114.29px

所以用元素的宽度减去超出部分的宽度,得到最后的元素宽度,三个元素的宽度分别为:142.86px 171.42px 285.72px。

如果设置了弹性容器的子元素的 flex-shrink ,要想计算宽度的话,要用元素的宽度 - (元素的宽度 * flex-shrink) /(元素1的宽度 * 元素1的flex-shrink + 元素2的宽度 * 元素2的 flex-shrink + ... )* (所有元素宽度之和 - 容器宽度)。

我们继续校验这个说法的正确与否,我们修改红色元素的宽度为 200px 且 flex-shrink 为 1,黄色元素的宽度为 300px 且 flex-shrink 为 2,绿色元素的宽度为 400px 且 flex-shrink 为 3,得到下面这个效果:

image.png

我们根据上面的计算公式进行计算,先计算红色的宽度。

200 - (200 * 1) / (200 * 1 + 300 * 2 + 400 * 3) * (200 + 300 + 400 - 600) = 200 - 200 / 2000 * 300 = 200 - 30 = 170px

以此类推,黄色元素宽度为 210px,绿色元素 220px。

flex-basis

该属性用来定义元素的初始宽或者高,是一个number类型,可以是百分比,也可以是数值,默认值为 auto。 当为auto时,元素的尺寸和元素内容自身尺寸相关。

flex

该属性用于设置元素如何分配容器空间,它是有 flex-grow、 flex-shrink 和 flex-basis 三个属性组成的组合属性,默认值为 0 1 auto

该属性有两个可能常用到的属性:

flex: none ====> flex: 0 0 auto

flex: auto ====> flex: 1 1 auto

algin-self

该属性用于定义某元素在容器中与其他元素不一样的交叉轴对齐方式,默认值为 auto,该默认值表示继承容器的 algin-items 属性。

该属性有6个属性值:

  • auto
  • stretch
  • center
  • flex-start
  • flex-end
  • baseline

可参考容器的 algin-items 属性效果图进行了解。

总结

本文小结

  1. 使用弹性布局,需要定义容器的布局方式为:display:flex
  2. flex-wrap来控制容器换行;
  3. 容器中的主轴需要通过flex-direction来控制,row是水平,column是垂直,使用-reverse来调节起终点方向;
  4. 知道主轴方向后,使用justify-content 控制主轴对齐方式;
  5. align开头的容器属性控制交叉轴;
  6. align-content多交叉轴效果才明显;

flex布局的常见用法

元素居中

效果图如下:

居中.png

主要CSS代码如下:

.parent {
    display: flex;
}
.children {
    margin: auto;
}

防止文件名过长省略格式

效果图如下:

防文件名溢出.jpg

HTML 代码如下:

<div class="file-item">
    <span class="file-name">成长是孩子自己的旅程.</span>
    <span class="file-format">pdf</span>
</div>

CSS 代码如下:

.file-item {
    display: flex;
    min-width: 0px;
    max-width: 150px;
}
.file-name {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
}
.file-format {
    flex-shrink: 0;
}

弹性布局 Flex 的优缺点

优点: flex布局学习简单,只要明白了基本属性和概念,基本很容易就能上手实现某些布局效果。

缺点: 兼容性比较差,只能兼容到 ie9 及以上。