【重学CSS】图文并茂!一次性搞懂 Flex 布局,实现“布局自由”~

3,942 阅读20分钟

本文正在参加「金石计划」

flag:每月至少产出三篇高质量文章~

欢迎关注我的另外几篇文章:

上一篇详细总结了 Gird 布局的相关知识,虽然 Grid 可以做的事情很多,但是 Flex 布局能做的也不少,这篇就来总结一下 Flex 布局 ~ 先来看一个炫酷的Demo:

1、flexbox 简介

Flexible Box Module,通常简称为 flexbox,被设计为一维布局模型,是一种可以提供界面中项目之间空间分配和强大对齐能力的方法。当我们将 flexbox 描述为一维时,我们是在描述 flexbox 一次处理一维布局的事实——无论是作为行还是作为列。这可以与CSS Grid Layout的二维模型进行对比,后者将列和行一起控制。 —— Mozilla mdn

传统的布局方案基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。Flexbox 布局(Flexible Box) 提供一种更有效的方式来布置、对齐和分配容器中项目的空间,即使它们的尺寸是未知的、动态的(因此有 "flex" 一词)。

弹性布局背后的主要思想是让容器有能力改变其项目的宽度/高度(和顺序),以更好地填充可用空间(主要是为了适应各种显示设备和屏幕尺寸)。一个灵活的容器可以扩展项目以填充可用的自由空间,也可以收缩项目以防止溢出。

相对于常规布局(基于垂直方向的block和基于水平方向的inline),flexbox布局是不分方向的。虽然这些常规布局对页面来说效果很好,但它们缺乏灵活性,无法支持大型或复杂的应用(尤其是在改变方向、调整大小、拉伸、缩小等方面)。

注意Flexbox 布局最适合于应用程序的组件,以及小规模的布局,而网格布局则是为了大规模的布局。

如果常规布局是基于块状和内联的流动方向,弹性布局则是基于 "弹性流动方向"。其中主要的概念如下图中所示:

图片来:flex-grammar - ruanyifeng

项目将按照主轴或交叉轴进行布局:

  • main axis: 弹性容器的主轴。

  • main-start | main-end: 弹性项目在容器的主轴方向上从 main-start 开始,到 main-end 进行排列。

  • main size: 弹性项目(flex item)的宽度或高度,以主尺寸中的那个为准,是项目的主要尺寸。弹性项目的主要尺寸属性是“宽度”或“高度”属性,以主要尺寸中的那个为准。

  • cross axis: 垂直于主轴的轴被称为交叉轴。它的方向取决于主轴的方向。

  • cross-start | cross-end: 弹性项目在容器的交叉轴方向上从 cross-start 开始,到 cross-end 进行排列。

  • cross size: 交叉轴的宽度或高度。

浏览器支持方面,可以在 Caniuse 中看到,主流浏览器几乎都对它有很好的支持了~

image.png

2、Flex 容器(Container)属性

2.1 display

设置容器为 Flex布局,可以取值为 flexinline-flex

.container {
  display: flex; /* or inline-flex */
}

2.2 flex-direction

flex-direction 用来确定弹性容器中主轴的方向。Flexbox 是单向布局,主要以水平行布局或垂直列布局。

.container {
  flex-direction: row | row-reverse | column | column-reverse;
}
  • row(默认): 水平方向为主轴,方向为从左至右
  • row-reverse: 水平方向为主轴,方向为从右至左
  • column: 垂直方向为主轴,方向为从上至下
  • column-reverse: 垂直方向为主轴,方向为从下至上

显示了 flex-direction 的四个可能值:从上到下、从下到上、从右到左和从左到右

主轴和交叉轴是Flex布局中的两个重要概念。主轴是Flex容器中子元素排列的方向,交叉轴是垂直于主轴的方向。例如,如果你设置了flex-direction: row,那么主轴就是水平方向,交叉轴就是垂直方向。如果你设置了flex-direction: column,那么主轴就是垂直方向,交叉轴就是水平方向。

可以通过不同的属性来控制子元素在主轴和交叉轴上的对齐方式。例如,justify-content用于设置主轴上的对齐方式,align-items用于设置交叉轴上的对齐方式。

2.3 flex-wrap

默认情况下,弹性项目会默认尝试将全部的项目放在一行中。flex-wrap 用来设置是否换行,可以取值为nowrap(不换行)wrap(换行)wrap-reverse(反向换行)

.container {
    flex-wrap: nowrap | wrap | wrap-reverse;
}
  • nowrap(默认):所有弹性项目都在一行上,可能导致容器溢出
  • wrap: 弹性项目将换行到多行,从上到下,方向由 flex-direction 定义
  • wrap-reverse: 弹性项目将从下到上换行到多行,与 flex-direction 定义的方向相反

两排盒子,第一排包裹到第二排

下面看一个demo,来实际感受这三个属性值的区别:

会发现 wrapwrap-reverse 其实是镜像的,你只要记得 wrap 是怎么换行,那就知道 wrap-reverse 是怎么换行的。

2.4 flex-flow

flex-flowflex-directionflex-wrap 属性的简写,默认值是 row nowrap

.container {
  flex-flow: column wrap;
}

2.5 justify-content

justify-content 定义了主轴上子元素的对齐方式。

.container {
    justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly | start | end | left | right ... + safe | unsafe;
  
    /* Positional alignment */
    justify-content: center;     /* 居中排列 */
    justify-content: start;      /* Pack items from the start */
    justify-content: end;        /* Pack items from the end */
    justify-content: flex-start; /* 从行首起始位置开始排列 */
    justify-content: flex-end;   /* 从行尾位置开始排列 */
    justify-content: left;       /* Pack items from the left */
    justify-content: right;      /* Pack items from the right */

    /* Baseline alignment */
    justify-content: baseline;
    justify-content: first baseline;
    justify-content: last baseline;

    /* Distributed alignment */
    justify-content: space-between;  /* 均匀排列每个元素
                                       首个元素放置于起点,末尾元素放置于终点 */
    justify-content: space-around;  /* 均匀排列每个元素
                                       每个元素周围分配相同的空间 */
    justify-content: space-evenly;  /* 均匀排列每个元素
                                       每个元素之间的间隔相等 */
    justify-content: stretch;       /* 均匀排列每个元素
                                       'auto'-sized 的元素会被拉伸以适应容器的大小 */

    /* Overflow alignment */
    justify-content: safe center;
    justify-content: unsafe center;

    /* Global values */
    justify-content: inherit;
    justify-content: initial;
    justify-content: unset;
}
  • flex-start(默认):从行首开始排列。每行第一个弹性元素与行首对齐,同时所有后续的弹性元素与前一个对齐。

  • flex-end:从行尾开始排列。每行最后一个弹性元素与行尾对齐,其他元素将与后一个对齐。

  • start:从行首开始排列。每行第一个元素与行首对齐,同时所有后续的元素与前一个对齐。

  • end: 从行尾开始排列。每行第一个元素与行尾对齐,同时所有后续的元素与前一个对齐。

  • left:伸缩元素一个挨一个在对齐容器得左边缘,如果属性的轴与内联轴不平行,则left的行为类似于start

  • right:元素以容器右边缘为基准,一个挨着一个对齐,如果属性轴与内联轴不平行,则right的行为类似于end

  • center:伸缩元素向每行中点排列。每行第一个元素到行首的距离将与每行最后一个元素到行尾的距离相同。

  • space-between:在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素与行首对齐,每行最后一个元素与行尾对齐。

  • space-around:在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素到行首的距离和每行最后一个元素到行尾的距离将会是相邻元素之间距离的一半。。

  • space-evenly:flex 项都沿着主轴均匀分布在指定的对齐容器中。相邻 flex 项之间的间距,主轴起始位置到第一个 flex 项的间距,主轴结束位置到最后一个 flex 项的间距,都完全一样。

  • baseline / first baseline / last baseline:指定参与第一或最后基线对齐:将盒子的第一或最后基线集的对齐基线与它的基线共享组中所有盒子的第一或最后基线集中的相应基线对齐。第一条基线的回退对齐是开始,最后一条基线的回退对齐是结束。

  • stretch:如果项目的总和尺寸小于对齐容器的尺寸,任何自动尺寸的项目都会被平均增加(而不是按比例增加),同时仍然受到max-height/max-width(或同等功能)所施加的限制,这样总和尺寸就会沿着主轴完全充满对齐容器。

  • safe:与对齐关键字一起使用,如果选定的关键字会导致元素溢出容器造成数据丢失,那么将会使用 start 代替它。

  • unsafe:无论项目和对齐容器的相对大小如何,给定的对齐值都会得到遵守。

展示不同间距选项的弹性容器内的弹性项目

请注意,主流浏览器对这些值的支持还不是那么完善的。例如,space-between从未得到某些版本的 Edge 的支持。MDN有详细的图表。最安全的值是flex-startflex-endcenter

还有两个额外的关键字可以与这些值配对:safeunsafe。使用safe可确保无论您如何进行此类定位,都不能将元素推送到屏幕外(例如,从顶部),从而使内容无法滚动(称为“数据丢失”) .

2.6 align-items

align-items 定义了交叉轴上的子元素的对齐方式。

.container {
  align-items: stretch | flex-start | flex-end | center | baseline | first baseline | last baseline | start | end | self-start | self-end + ... safe | unsafe;
  
    /* Basic keywords */
    align-items: normal;
    align-items: stretch;

    /* Positional alignment */
    align-items: center; /* Pack items around the center */
    align-items: start; /* Pack items from the start */
    align-items: end; /* Pack items from the end */
    align-items: flex-start; /* Pack flex items from the start */
    align-items: flex-end; /* Pack flex items from the end */
    align-items: self-start;
    align-items: self-end;

    /* Baseline alignment */
    align-items: baseline;
    align-items: first baseline;
    align-items: last baseline; /* Overflow alignment (for positional alignment only) */
    align-items: safe center;
    align-items: unsafe center;

    /* Global values */
    align-items: inherit;
    align-items: initial;
    align-items: unset;
}

不同对齐选项的演示,比如所有盒子都粘在弹性父级的顶部、底部、拉伸或沿基线

  • normal:这个关键字的效果取决于我们处在什么布局模式中:

    • 在绝对定位的布局中,对于被替代的绝对定位盒子,这个效果和start?的效果的一样;对于其他所有绝对定位的盒子,这个效果和stretch的效果一样。
    • 在绝对定位布局的静态位置上,效果和stretch一样。
    • 对于那些弹性项目而言,效果和stretch一样。
    • 对于那些网格项目而言,效果和stretch一样,除了有部分比例或者一个固定大小的盒子的效果像start
    • 这个属性不适用于块级盒子和表格。
  • stretch(默认):弹性项包含外边距的交叉轴尺寸被拉升至行高

  • flex-start / start / self-start:元素向侧轴起点对齐。

  • flex-end / end / self-end:元素向侧轴终点对齐。

  • center:元素在侧轴居中。如果元素在侧轴上的高度高于其容器,那么在两个方向上溢出距离相同。

  • baseline / first baseline / last baseline:所有元素向基线对齐。侧轴起点到元素基线距离最大的元素将会于侧轴起点对齐以确定基线。

2.7 align-content

align-content 用来定义多根轴线的对齐方式,可以取值与justify-content相同,但是只在有多根轴线时生效。

注意: 该属性只对多行 flex 容器有效,其中flex-wrap设置为 或wrapwrap-reverse。单行 flex 容器(即 flex-wrap 设置为其默认值 no-wrap)不会存在 align-content

.container {
    align-content: flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | start | end | baseline | first baseline | last baseline + ... safe | unsafe;
  
    /* 基本位置对齐 */
    /*align-content 不采用左右值 */
    align-content: center;     /* 将项目放置在中点 */
    align-content: start;      /* 最先放置项目 */
    align-content: end;        /* 最后放置项目 */
    align-content: flex-start; /* 从起始点开始放置 flex 元素 */
    align-content: flex-end;   /* 从终止点开始放置 flex 元素 */

    /* 默认对齐 */
    align-content: normal;

    /*基线对齐*/
    align-content: baseline;
    align-content: first baseline;
    align-content: last baseline;

    /* 分布式对齐 */
    align-content: space-between; /* 均匀分布项目
                                     第一项与起始点齐平,
                                     最后一项与终止点齐平 */
    align-content: space-around;  /* 均匀分布项目
                                     项目在两端有一半大小的空间*/
    align-content: space-evenly;  /* 均匀分布项目
                                     项目周围有相等的空间 */
    align-content: stretch;       /* 均匀分布项目
                                     拉伸‘自动’ - 大小的项目以充满容器 */

    /* 溢出对齐 */
    align-content: safe center;
    align-content: unsafe center;

    /* 全局属性 */
    align-content: inherit; /* 继承 */
    align-content: initial;  /* 初始值 */
    align-content: unset; /* 未设置 */
}
  • normal(默认):这些项按默认位置填充,就像没有设置对齐内容值一样。

  • flex-start: 所有行从垂直轴起点开始填充。第一行的垂直轴起点边和容器的垂直轴起点边对齐。接下来的每一行紧跟前一行。

  • flex-end: 所有行从垂直轴末尾开始填充。最后一行的垂直轴终点和容器的垂直轴终点对齐。同时所有后续行与前一个对齐。

  • start: 所有行从容器的起始边缘开始填充。

  • end: 所有行从容器的结束边缘开始填充。

  • baseline / first baseline / last baseline:指定参与第一或最后基线对齐:将盒子的第一或最后基线集的对齐基线与它的基线共享组中所有盒子的第一或最后基线集中的相应基线对齐。第一条基线的回退对齐是开始,最后一条基线的回退对齐是结束。

  • center:所有行朝向容器的中心填充。每行互相紧挨,相对于容器居中对齐。容器的垂直轴起点边和第一行的距离相等于容器的垂直轴终点边和最后一行的距离。

  • space-between:所有行在容器中平均分布。相邻两行间距相等。容器的垂直轴起点边和终点边分别与第一行和最后一行的边对齐。

  • space-around:所有行在容器中平均分布,相邻两行间距相等。容器的垂直轴起点边和终点边分别与第一行和最后一行的距离是相邻两行间距的一半。

  • space-evenly:所有行沿垂直轴均匀分布在对齐容器内。每对相邻的项之间的间距,主开始边和第一项,以及主结束边和最后一项,都是完全相同的。

  • stretch: 拉伸所有行来填满剩余空间。剩余空间平均地分配给每一行。

  • safe:与对齐关键字一起使用。如果所选的关键字意味着项溢出对齐容器(data loss),则将采用备用策略对项进行对齐,就像启动了 start 对齐模式一样。

  • unsafe:与对齐关键字一起使用。无论元素和对齐容器的相对大小如何、是否会导致一些元素溢出可见范围(data loss),都使用给定的对齐值。

align-content 属性的示例,其中一组项目聚集在顶部或底部,或伸展以填充空间,或具有间距。

下面来看一个demo:

2.8 gap, row-gap, column-gap

gap属性row-gapcolumn-gap 的缩写,指定了 gutters 的大小,也就是网格、flex和多列布局中行和列之间的空间。

image.png

gap属性明确控制弹性项目之间的空间。它仅适用于不在外边缘的项目之间的间距。

.container {
  display: flex;
  ...
  gap: 10px;
  gap: 10px 20px; /* row-gap column gap */
  row-gap: 10px;
  column-gap: 20px;
}

下面来看一个demo:

3、Flex 项目(Items)属性

3.1 order

order 用来定义子元素的排列顺序,可以取任意整数,默认为0,数值越小越靠前。默认情况下,弹性项目按源代码顺序排列。但是,该order属性控制它们在 flex 容器中出现的顺序。

.item {
  order: 5; /* default is 0 */
}

显示 flexbox 顺序的图表。 一个容器,其项目为 1 1 1 2 3、-1 1 2 5 和 2 2 99。

相同的项目order恢复到源顺序。

3.2 flex-grow

flex-grow 用来设置项目在 flex 容器中分配剩余空间的相对比例。默认为0,表示如果存在剩余空间也不放大。

剩余空间是 flex 容器的大小减去所有 flex 项的大小加起来的大小。如果所有项目都flex-grow设置为1,则容器中的剩余空间将平均分配给所有项目。如果其中一个项目的值为2,则该项目将占用其他孩子的两倍空间(或者至少会尝试)。

.item {
    flex-grow: 4; /* default 0 */
  
    /* <number> 值 */
    flex-grow: 3;
    flex-grow: 0.6;

    /* 全局值 */
    flex-grow: inherit;
    flex-grow: initial;
    flex-grow: revert;
    flex-grow: unset;
}

两行项目,第一行具有相同大小的所有项目,具有相同的 flex-grow 数字,第二行具有中心项目的两倍宽度,因为它的值是 2 而不是 1。

注意:设置为负数的无效的。flex-grow 与其他的 flex 属性flex-shrinkflex-basis一起使用,通常使用flex 来简写定义,以确保所有的值都被设置。

下面来看一个demo:

3.3 flex-shrink

flex-shrink 属性指定了 flex 元素的收缩规则。flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。默认为 1,即如果空间不足会自动缩小。

.item {
    flex-shrink: 3; /* default 1 */
  
    flex-shrink: 2;
    flex-shrink: 0.6;

    /* Global values */
    flex-shrink: inherit;
    flex-shrink: initial;
    flex-shrink: unset
}

注意:设置为负数的无效的。

下面来看一个demo:

3.4 flex-basis

flex-basis 指定了 flex 元素在主轴方向上的初始大小。如果不使用 box-sizing 改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒(content-box)的尺寸。默认为auto,即根据内容自适应。

备注:  flex-basis 简史

  • 最初,"flex-basis:auto" 的含义是 "参照我的widthheight属性".
  • 在此之后,"flex-basis:auto" 的含义变成了自动尺寸,而 "main-size" 变成了 "参照我的widthheight属性"。实际执行于 bug 1032922.
  • 然后呢,这个更改又在 bug 1093316 中被撤销了,所以 "auto" 变回了原来的含义; 而一个新的关键字 'content' 变成了自动尺寸。 (bug 1105111 包括了增加这个关键字).
.item {
    flex-basis:  | auto; /* default auto */
    
    /* 指定<'width'> */
    flex-basis: 10em;
    flex-basis: 3px;
    flex-basis: auto;

    /* 固有的尺寸关键词 */
    flex-basis: fill;
    flex-basis: max-content;
    flex-basis: min-content;
    flex-basis: fit-content;

    /* 在 flex item 内容上的自动尺寸 */
    flex-basis: content;

    /* 全局数值 */
    flex-basis: inherit;
    flex-basis: initial;
    flex-basis: unset;
}

备注:  当一个元素同时被设置了 flex-basis (除值为 auto 外) 和 width (或者在 flex-direction: column 情况下设置了height) , flex-basis 具有更高的优先级。

下面来看一个demo:

如果设置为0,则不考虑内容周围的额外空间。如果设置为auto,则根据其flex-grow值分配额外空间。看这张图。

image.png

3.5 flex

flexflex-growflex-shrinkflex-basis 的简写形式,默认值为 0 1 auto。第二个和第三个参数(flex-shrinkflex-basis)是可选的。

flex 属性的默认值为0 1 auto,但如果您将其设置为单个数值,例如flex: 5;,则会将 flex-basis 更改为 0%,因此等价于设置flex-grow: 5; flex-shrink: 1; flex-basis: 0%;

.item {
    flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

3.6 align-self

align-self 会对齐当前 flex 行中的元素,并覆盖已有的 align-items 的值。在网格中,它使项目在网格区域内对齐。在 Flexbox 中,会按照 交叉轴(当前 flex 元素排列方向的垂直方向)进行排列。

.item {
  align-self: auto | flex-start | flex-end | center | baseline | stretch;
}

具有 align-self 值的一个项目位于 flex parent 的底部,而不是所有其余项目所在的顶部。

下面看一个demo:

请注意floatclearvertical-align对弹性项目没有影响。

4、Flex 布局和 Grid 布局之间的区别

flexboxgrid 之间有很多相似之处,在前端布局中比之前出现的任何布局技术都强大得多。它们可以拉伸和收缩,可以居中,可以重新排序,可以对齐等待。下面我们看看它们之间有什么差异:

4.1 Flexbox 可以选择性地换行

如果我们允许 flex 容器换行,当 flex 项目填满一行时,它们将换行到另一行。它们在下一行的排列位置与第一行发生的情况无关,从而呈现出类似砖石结构的外观。

网格也可以选择换行(如果我们允许自动填充),因为项目可以填充一行并移动到新行(或自动放置它们自己),但是当它们这样做时,它们将沿着相同的网格线排列元素做。

image.png

4.2 Flex 是一维布局,而 Grid 是二维布局

虽然 flexbox 可以在允许元素换行,某种意义上创建了行和列这种二维布局,但无法以声明方式控制元素的最终位置,因为元素仅沿单个轴排列,然后相应地换行或不换行。如果主轴够长的话,它们会沿着一维平面做它们做的事情,这是因为我们可以选择做一些事情的单一维度,比如沿基线对齐元素——这是网格无法做到的。

.parent {
  display: flex;
  flex-flow: row wrap; /* OK elements, go as far as you can on one line, then wrap as you see fit */
}

你可以将网格视为”二维“,因为我们可以声明行和列的大小,然后根据我们的选择明确地将内容放入行和列中。

.parent {
  display: grid;
  grid-template-columns: 1fr 3fr 1fr; 
  grid-template-rows: 200px auto 100px; 
  grid-template-areas:
    "header header header"
    ". main sidebar"
    "footer . .";
}

.child-1 {
  grid-area: header;
}

.child-2 {
  grid-area: main;
}

.child-3 {
  grid-area: sidebar;
}

.child-4 {
  grid-area: footer;
}

image.png

网格主要是在父元素上定义的。在 flexbox 中,大部分布局(超出最基本的布局)都发生在 children 上。

.flexbox {
  display: flex;
  
  > div {
  
    &:nth-child(1) { // logo
      flex: 0 0 100px;
    }
    
    &:nth-child(2) { // search
      flex: 1;
      max-width: 500px;
    }
    
    &:nth-child(3) { // avatar
      flex: 0 0 50px;
      margin-left: auto;
    }
  }
}

.grid {
  display: grid;
  grid-template-columns: 1fr auto minmax(100px, 1fr) 1fr;
  grid-template-rows: 100px repeat(3, auto) 100px;
  grid-gap: 10px;
}

4.3 Grid 更擅长重叠

让元素在 flexbox 中重叠需要查看传统的东西,如负边距、transforms 或绝对定位,以打破 flex 行为。使用网格,我们可以将项目放置在重叠的网格线上,甚至可以放在完全相同的网格单元格中。

image.png

下面看这个demo:

4.4 Grid 更稳固

虽然 flexbox 的灵活性有时是它的优势,但一个 flex项目 的大小是相当复杂的。它是widthmin-widthmax-widthflex-basisflex-growflex-shrink的组合,更不用说里面的内容和留白等事项,以及同一行的其他项目。网格有有趣的空间占用功能,比如小数单位,以及内容打破网格的能力,不过,一般来说,我们是在设置网格线,并在其中放置项目,这些项目会直接到位。

4.5 Flex 可以把东西推开

这是 flexbox 的一个相当独特的功能,例如,你可以在一个元素上放 margin-right: auto;,如果有空间的话,这个元素会把其他东西推得越远越好。

4.6 用 Flex 还是 Grid ?

有个推特大佬说的挺好:

image.png

意思是:Flexbox是用于对齐。CSSGrid是用来布局的。

当你心里已经有了布局结构时,就使用 Grid 布局,而当你只想让所有东西都fit时,就使用 Flex 布局布局优先 vs 内容优先

5、推荐几个可视化学习 Flexbox 布局的站点

  1. progressivered.com - Flex Layout Attribute
  2. flexboxpatterns

end~