图文+N种经典案例,这一次彻底搞懂Flex布局

102 阅读10分钟

切片 2.png

前言

每次使用Flex布局时,总会在网上搜一堆资料,自己不会写?即使效果出来了,涉及到的属性配置也是一知半解?我就是这种情况。

根据我个人的经验总结,主要有2点,1理解的不够透彻,2没有对知识点进行系统性的梳理和归纳总结。拒绝 Copy大法,眼会手不会,别人开口跪,咱们开口卒。再简单的知识点都要过手。这一次就让我们彻底搞懂Flex,轻松实现各种响应式页面布局。

完整demo和案例可以查看 github源代码


学习思路

第一步:观其全貌。先梳理出一个完整的知识体系,一般我会画画图,比如思维导图、知识体系图等等;

第二步:知识点拆分。好比一棵树,从根节点开始,下面是一级知识点列表,一级下面可能有多个二级知识点列表,二级下面可能有多个三级知识点列表,以此类推;

第三步:落地到最小单元。好比一棵树,叶子节点就是最小单元,搞清楚它的作用,尽量写一个demo,加深理解。

针对 Flex,首先,我画了一个核心概念图,对它有一个大而全的了解;其次,罗列出容器和元素上都有哪些属性,每个属性对应有哪些可选的属性值;最后,再结合 属性 + 属性值 的最小配置单元,查看对应的布局效果,最终,达到熟练实现各种页面布局效果的能力。


核心概念图

flex语法图解.png

这张图包含了 Flex 最核心的概念。接下来,我们逐一进行拆分讲解:

  1. Flex 布局是 简单、高效、响应式布局;
  2. Flex Container 被称为 Flex 容器,即需要声明 display: flex
  3. 里面的元素 Flex Item 被称为 Flex 元素,这里需要强调一下,只有容器的直系子元素才能被称为 Item;
  4. CSS Flexbox 是一种一维的布局模型。包含2根轴线,Main Axis 被称为主轴,Cross Axis 被称为交叉轴;
  5. 过去,CSS的书写模式主要被认为是水平的,从左到右的。而 Flex 涵盖了书写模式的范围,所以我们不再假设一行文字是从文档的左上角开始向右书写, 新的行也不是必须出现在另一行的下面。它包含了 Start(起始线)和 End(终止线),后面会详细讲解;
  6. 容器(Container)上有5个属性配置项和 1 个属性简写配置项;
  7. 元素(Item)上有3个重要属性配置项和 2 个不常用属性配置项。

Flex Container 容器配置项

{
  flex-direction: row | row-reverse | column | column-reverse;
  flex-wrap: nowrap | wrap | wrap-reverse;
  flex-flow: <'flex-direction'> <'flex-wrap'>;
  justify-content: flex-start | flex-end | center | space-between | space-around; 
    
  align-items: flex-start | flex-end | center | baseline | stretch; 
  align-content: flex-start | flex-end | center | space-between | space-around | stretch; 
    
}

有一个规律方便我们理解和记忆,flex 和 justify 开头的是设置主轴的方向和排列方式以及是否换行。align 开头的是设置交叉轴的排列方式。

一共包含 6 个可选属性,以及每个属性对应的可选值。接下来,逐一对每个 属性 + 属性值 进行 demo 测试!


flex-direction 属性

flex-direction: row | row-reverse | column | column-reverse;

设置主轴方向:inline 向右 | inline 向左 | block 向下 | block 向上;

默认配置:{ flex-direction: row; }


<!-- 主轴方向 -->
<h3>flex-direction: row | row-reverse | column | column-reverse;</h3>
<div class="main-flex">
  <div class="flex-container" style="flex-direction: row">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
  </div>
  <div class="flex-container" style="flex-direction: row-reverse">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
  </div>
  <div class="flex-container" style="flex-direction: column">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
  </div>
  <div class="flex-container" style="flex-direction: column-reverse">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
  </div>
</div>

iShot2022-02-10 18.15.46.png

从左到右依次为 row、row-reverse、column、column-reverse。要把效果图和对应的属性配置项结合起来阅读。需要注意一点,确定了主轴,也就确定了交叉轴。


flex-wrap 属性

flex-wrap: nowrap | wrap | wrap-reverse;

设置主轴排列是否换行:不换行 | 换行 | 反向排序并换行;

默认配置:{ flex-wrap: nowrap; }

<h3>flex-wrap: nowrap | wrap | wrap-reverse;</h3>
<div class="main-flex">
  <div class="main-flex">
      <div class="flex-container" style="flex-wrap: nowrap;">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
      </div>
      <div class="flex-container" style="flex-wrap: nowrap;">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
        <div class="flex-item">8</div>
      </div>
      <div class="flex-container" style="flex-wrap: wrap;">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
        <div class="flex-item">8</div>
      </div>
      <div class="flex-container" style="flex-wrap: wrap-reverse;">
        <div class="flex-item">1</div>
        <div class="flex-item">2</div>
        <div class="flex-item">3</div>
        <div class="flex-item">4</div>
        <div class="flex-item">5</div>
        <div class="flex-item">6</div>
        <div class="flex-item">7</div>
        <div class="flex-item">8</div>
      </div>
    </div>
</div>

iShot2022-02-11 09.31.42.png

默认样式是不换行。当我们设置为 nowrap,如果容器的宽度足够,元素的宽度即为自身宽度。当容器不足时,元素宽度会等比例缩小(后面会讲到flex: 0 1 auto);当我们设置为 wrap 时,元素宽度不会被压缩,但是有一个不足的地方,容器右侧留有空白区域,社区大佬有什么好的解决方案吗?


flex-flow 属性

flex-flow: <'flex-direction'> <'flex-wrap'>;

组合配置,简化写法。

默认配置:{ flex-flow: row nowrap; }


justify-content 属性

justify-content: flex-start | flex-end | center | space-between | space-around;

设置主轴上内容的排列方式:起始线 | 终止点 | 居中 | 两侧分布 | 等间隔分布;

默认配置:{ justify-content: flex-start; }

<h3>justify-content: flex-start | flex-end | center | space-between | space-around; </h3>
<div class="main-flex">
  <div class="flex-container" style="justify-content: flex-start">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
  </div>
  <div class="flex-container" style="justify-content: flex-end">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
  </div>
  <div class="flex-container" style="justify-content: center">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
  </div>
  <div class="flex-container" style="justify-content: space-between">
    <div class="flex-item">
      <button>1</button>
      <button>2</button>
    </div>
    <div class="flex-item" style="text-align: right;">
      <button>3</button>
      <button>4</button>
    </div>
  </div>
  <div class="main-flex">
    <div class="flex-container" style="justify-content: space-around;">
      <div class="flex-item">1</div>
      <div class="flex-item">2</div>
      <div class="flex-item">3</div>
    </div>
  </div>
</div>

iShot2022-02-10 18.20.56.png

实际项目中,当只有一行内容布局时,居中分布、两侧分布、等间隔分布都会经常使用。


align-items 属性

align-items: flex-start | flex-end | center | baseline | stretch;

设置交叉轴排列方式:容器顶部 | 底部 | 垂直居中 | 基线 | 撑满整个高度;

默认配置:{ align-items: flex-start; }

<h3>align-items: flex-start | flex-end | center | baseline | stretch; </h3>
<div class="main-flex">
  <div class="flex-container" style="align-items: flex-start;">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item" style="height: 60px;">3</div>
  </div>
  <div class="flex-container" style="align-items: flex-end;">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item" style="height: 60px;">3</div>
  </div>
  <div class="flex-container" style="align-items: center;">
    <div class="flex-item">50px</div>
    <div class="flex-item" style="height: 60px;">60px</div>
    <div class="flex-item" style="height: auto;">auto</div>
  </div>
  <div class="flex-container" style="align-items: baseline;">
    <div class="flex-item">1</div>
    <div class="flex-item" style="padding-top: 10px;">2</div>
    <div class="flex-item" style="height: 60px; padding-top: 16px;">第一行文字对齐</div>
  </div>
  <div class="flex-container" style="align-items: stretch;">
    <div class="flex-item">50px</div>
    <div class="flex-item" style="height: 60px;">60px</div>
    <div class="flex-item" style="height: auto;">auto</div>
  </div>
</div>

iShot2022-02-11 10.02.48.png

这里面唯一不太好理解的是 stretch 属性值。如果元素 Item 设置了高度,正常显示;如何没有设置高度,或者覆盖原来的 height 属性,设置为 height: auto; 那么就会撑满整个容器 Container 的高度;如果不设置 stretch 属性值,那么元素的高度即为被内容撑开的高度,如下图所示。

<div class="flex-container" style="align-items: flex-start;">
  <div class="flex-item">1</div>
  <div class="flex-item">2</div>
  <div class="flex-item">3</div>
</div>
<div class="flex-container" style="align-items: flex-start;">
  <div class="flex-item">1</div>
  <div class="flex-item">2</div>
  <div class="flex-item" style="height: auto;">3</div>
</div>

iShot2022-02-11 10.09.03.png


align-content 属性

align-content: flex-start | flex-end | center | space-between | space-around | stretch;

设置项目在交叉轴上排列方式:起始点 | 终止点 | 中点 | 均匀分布 | 等间隔分布 | 拉伸‘自动’-大小的项目以充满容器;

前置条件:多行弹性盒子模型才有效,需要设置 flex-wrap: nowrap;单行无效,单行设置使用 align-items。

默认配置:{ align-content: flex-start; }

<h3>align-content: flex-start | flex-end | center | space-between | space-around | stretch; </h3>
<div class="main-flex align-content">
  <div class="flex-container" style="align-content: flex-start">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
  </div>
  <div class="flex-container" style="align-content: flex-end">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
  </div>
  <div class="flex-container" style="align-content: center">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
  </div>
  <div class="flex-container" style="align-content: space-between">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
  </div>
  <div class="flex-container" style="align-content: space-around">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
  </div>
  <div class="flex-container" style="align-content: stretch">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item" style="height: auto;">6</div>
  </div>
</div>

image.png

结合示例,能够更好的理解各个属性值的作用。


Flex Item 元素配置项

{
  flex-grow: <number>;
  flex-shrink: <number>;
  flex-basis: <width | content | fill | length> | auto;
  flex: <'flex-grow'> <'flex-shrink'>? || <'flex-basis'>;
  
  order: <number>;
  align-self: auto | normal | start | end | center | self-start;
}

一共包含 6 个可选属性,以及每个属性对应的可选值。我们只需要搞清楚 flex 开头的前面 4 个属性配置即可。


flex-grow 属性

flex-grow: <number>;

定义 Item 在容器中分配剩余空间的相对比例,grow 字面量含义即为成长,变长。默认为0(即为即使有剩余空间,也不会放大)。

默认配置:{ flex-grow: 0; }

<h3>flex-grow</h3>
<div class="main-flex">
  <div class="main-flex">
  <div class="flex-container">
    <div class="flex-item" style="flex-grow: 0;">固定 0</div>
    <div class="flex-item" style="flex-grow: 0;">固定 0</div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="flex-grow: 1;">自动撑开 1</div>
    <div class="flex-item">固定 0</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">固定 0</div>
    <div class="flex-item" style="flex-grow: 1;">自动撑开 1</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">固定 0</div>
    <div class="flex-item" style="flex-grow: 1;">自动撑开 1</div>
    <div class="flex-item">固定 0</div>
  </div>
</div>
</div>

image.png

轻松实现一侧固定,另一侧宽度自适应的布局,双飞燕布局(左右固定,中间宽度自适应)。


flex-shrink 属性

flex-shrink: <number>;

定义 Item 在容器中分配的缩小比例,shrink 字面量含义即为缩小。默认为1(Item 宽度总和大于容器宽度时,等比缩小 Item 宽度),设置为0,当宽度不够时,也不会压缩宽度。 默认配置:{ flex-shrink: 1; }

<div class="main-flex">
  <div class="flex-container">
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="flex-shrink: 0;">0</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="flex-shrink: 0;">0</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 1;">1</div>
    <div class="flex-item" style="flex-shrink: 2;">2</div>
    <div class="flex-item" style="flex-shrink: 2;">2</div>
  </div>
</div>

image.png

设置为 0 时,及时宽度不够,也不会被压缩;设置为2比设置为1压缩的比例更大,为它的2倍。


flex-basis 属性

flex-basis: <width | content | fill | length> | auto;

项目占据的主轴空间,默认 auto,即为本来的大小。

PS:flex-basis 的权重高于 Item 本身设置的 width 值。

默认配置:{ flex-basis: auto; }

<div class="main-flex">
  <div class="flex-container">
    <div class="flex-item" style="flex-basis: auto;">auto</div>
    <div class="flex-item" style="flex-basis: auto;">auto</div>
    <div class="flex-item" style="flex-basis: auto;">auto</div>
    <div class="flex-item" style="flex-basis: auto;">auto</div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
    <div class="flex-item" style="flex-basis: 24%;">24%</div>
  </div>
</div>

image.png

Item 的宽度是 50px,会被 flex-basis 设置值 24% 覆盖掉。


flex 属性

flex: <'flex-grow'> <'flex-shrink'>? || <'flex-basis'>;

组合写法,简化配置。这里的写法非常灵活,支持单值语法,双值语法,三值语法。具体可以查看MDN传送门。个人觉得没必要纠结用哪种,绝大情况下我们都采用默认配置,又或者单独修改某一项属性配置即可。

默认配置:{ flex: 0 1 auto; }


order 属性

order: <number>;

值越小,排序越靠前。默认为 0。

默认配置: { order: 0; }

<div class="main-flex">
  <div class="flex-container">
    <div class="flex-item">0</div>
    <div class="flex-item">0</div>
    <div class="flex-item">0</div>
  </div>
  <div class="flex-container">
    <div class="flex-item">order: 0</div>
    <div class="flex-item" style="order: -1;">order: -1</div>
    <div class="flex-item">order: 0</div>
  </div>
</div>

image.png

它的好处在于,比如页面上已有几个按钮,现在告诉你需要把某个按钮放在最前面,就可以使用 order 属性。实际项目建议还是别这样玩,容易麻痹队友,移动按钮位置会更好。


align-self 属性

align-self: auto | normal | start | end | center | self-start ...

覆盖 align-items 的值,个性化配置某一个元素在交叉轴上的对齐方式。

默认配置:{ align-items: auto; }

看完下面这个demo就明白什么意思了。

<div class="main-flex">
  <div class="flex-container">
    <div class="flex-item" style="align-self: auto;"></div>
    <div class="flex-item" style="align-self: auto;"></div>
    <div class="flex-item" style="align-self: auto;"></div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="align-self: auto;"></div>
    <div class="flex-item" style="align-self: center;">center</div>
    <div class="flex-item" style="align-self: auto;"></div>
  </div>
  <div class="flex-container">
    <div class="flex-item" style="align-self: auto;"></div>
    <div class="flex-item" style="align-self: end;">end</div>
    <div class="flex-item" style="align-self: auto;"></div>
  </div>
</div>

image.png


基础样式

demo的基础样式进行补充说明。

.main-flex {
  width: 100%;
  margin: 5px;
  display: flex;
  flex-wrap: wrap;
}

.flex-container {
  width: 400px;
  height: 100px;
  border: 1px solid #000;
  display: flex;
  margin: 10px;
}

.flex-item {
  width: 90px;
  height: 50px;
  border: 1px solid blue;
}
.item {
  width: 90px;
  border: 1px solid blue;
}

.align-content>.flex-container {
  height: 120px;
  flex-wrap: wrap;
}

练习篇

这是我之前在 CSDN传送门 上写的关于 Flex 文章,后续开发时还是不太会,说到底还是片面的学习态度,理解的不透彻。

image.png

若你能全部实现上面的效果,恭喜你,你已经完全掌握了 Flex,剩下的就是实战积累。


实战篇

垂直居中布局

实际项目中,遇到最多的就是图片,文字,div 混在一起实现垂直居中对齐,使用 Flex 就能轻松实现。

image.png

2个关键属性: align-items: center; justify-content: center;,主轴内容居中对齐,交叉轴元素居中对齐。

<div class="flex-container" style="align-items: center; justify-content: center;">
  <div class="flex-item"></div>
  <div class="flex-item" style="margin: 0 20px; height: 60px;"></div>
  <img src="./pic.jpg" height="50px" alt="">
  <span style="margin-left: 8px;">MDN</span>
</div>
列表渲染
.container {
  display: flex;
  border: 1px solid #000;
  flex-wrap: wrap;
  justify-content: center;
}

.item {
  width: 100px;
  height: 100px;
  border: 1px solid blue;
  margin: 10px;
}

<div class="container">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

image.png

实现如上的布局效果。优势在于响应式布局,当内容单元的尺寸固定时,可以根据容器宽度,最大程度展示更多的数据。劣势在于翻页最后一页,元素内容过少,整体布局不够优雅,但是也能接受,得看产品心情。如果实在接受不了,可以采用栅格布局,不足在于一行显示的数量是固定的几个值,无法最大程度的展示最多的数据。


双飞燕布局

参考前面讲解 flex-grow 时的 demo。关键属性 flex-grow

image.png


左右布局
<div class="container" style="justify-content: space-between;">
  <div class="btns-left">
    <button>1</button>
    <button>2</button>
  </div>
  <div class="btns-right">
    <button>3</button>
    <button>4</button>
  </div>
</div>

image.png

后台管理系统中,table表格上方有一行按钮集合,会把新增、批量修改,批量删除、上传等业务操作相关的按钮放在左边,把导出、下载等也业务无关的按钮放在右边。相比 float 而言,更能体现出 Flex 的简单、高效。

需要注意一点,不要设置父元素的高度,而是根据 Item 自动撑开。

最后,附上阮一峰老师的 Flex传送门