1.布局演变史

1)初代:table 布局
在网页布局没有进入 CSS 的时代,排版几乎是通过 table 元素实现的。虽然它可以很方便地实现水平和垂直对齐,但是缺点也很明显:
代码臃肿;不利于SEO;不够语义化;后期难以修改

2)第二代:div+css 布局
随着 Web 语义化的流行,CSS 标准为我们提供了 3 种布局方式:标准文档流、浮动布局和定位布局。这几种方式的搭配使用可以轻松搞定 PC 端页面的常见需求。然而,这些写法也存在一些缺陷:缺少语义并且不够灵活。

3)第三代:flex 布局
flex 布局属于一维布局,适合用于局部组件。目前在移动端布局日渐成为主流,也是本文重点。

4)第四代:grid布局
grid 布局属于二维布局,适合用于页面框架。目前兼容性不是很好,尚未完全普及。

2.flex 布局

Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。利用 Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持。

要使用 flex 布局,只需给元素设置 display:flex 或者 display:inline-flex 。前者会将元素作为块状弹性容器,若没有指定宽度,默认撑满一整行;后者会将元素作为内联弹性容器,若没有指定宽度,默认由内容撑开。

注意,设为 Flex 布局以后,子元素的 floatclearvertical-align 属性将失效。

2.1 基本概念

1)父容器和子项目

设置了 display:flex 或者 display:inline-flex 的元素将成为父容器 (flex container) ,其内部所有子元素成为子项目 (flex item)。

2)主轴和交叉轴

如下图所示:

  • 父容器存在两条轴,主轴(main axis)和交叉轴(cross axis)。
  • 主轴的开始位置叫做 main start,结束位置叫做 main end;交叉轴的开始位置叫做 cross start,结束位置叫做 cross end。主轴默认情况下水平向右,我们可以通过 flex-direction 指定它的方向,主轴方向确定后,我们进而可以得到交叉轴的方向。
  • 子项目默认沿主轴排列。单个项目占据的主轴空间叫做 main size,占据的交叉轴空间叫做 cross size

3)6 大容器属性

以下 6 个属性设置在父容器上:

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

flex-direction 属性定义主轴的方向,进而决定子项目的排列方向

row:
默认值。主轴水平向右,同时交叉轴垂直向下

row-reverse
主轴水平向左,同时交叉轴垂直向下

column
主轴垂直向下,同时交叉轴水平向右

column-reverse
主轴垂直向上,同时交叉轴水平向右

注意:只要主轴是 row,交叉轴就一定是向下的;而只要主轴是 column,交叉轴就一定是向右的。和所谓的逆时针、顺时针没关系。详细的解释另一篇博客有说明。

justify-content 属性定义子项目沿着主轴方向具体如何排列

flex-start:起始端对齐

flex-end:末尾端对齐

center: 居中对齐(用于实现水平居中)

space-between: 子项目和子项目的间距相等,首尾两端的子项目与父容器相切

space-around: 子项目和子项目的间距相等,首尾两端的子项目到父容器的距离是子项目间距的一半(注意 around 的意思,相当于以每个子项目为中心,会有一片环绕空间)

space-evenly:子项目和子项目的间距相等,首尾两端的子项目到父容器的距离和子项目间距一样

align-items 属性定义子项目沿着交叉轴方向具体如何排列

flex-start: 起始端对齐

flex-end: 末尾端对齐

center:居中对齐(用于实现垂直居中)

baseline: 基线对齐。以一开始是起始端对齐为例,cross-strat 到各个子项目基线的距离可能各不相同,一旦设置了基线对齐,则:距离最大的那个子项目保持与 corss-start 相切,其他子项目的基线均向该项目的基线对齐

stretch:子项目沿着交叉轴方向拉伸至与父容器尺寸一样(可用于实现等高布局)

flex-wrap 属性定义子项目是否换行、如何换行

nowrap:
不换行(默认)。也就是说父容器尺寸不够时,会为了达到不换行的效果而压缩子项目的尺寸

wrap
正常换行

wrap-reverse
逆序换行。即沿着交叉轴的反方向换行,如下图:

确定换行方向,也可以采用以下方法:

  • 首先确定正常换行情况下的排列方式
  • 保持第一行不动,将其他行沿着与主轴垂直的方向翻转

flex-flow 属性定义子项目如何流动,以及流动到终点是否换行。简单地说,它是 flex-directionflex-wrap 属性的结合。它的取值可以是:

  • row nowrap
  • row
  • wrap
    等等。

align-content 属性定义子项目存在多行时,行与行之间的对齐方式

flex-start:起始端对齐

flex-end:末尾端对齐

center:居中对齐

space-around:各行沿交叉轴均匀分布,位于首尾两端的行到父容器的距离是行与行距离的一半

space-between: 各行沿交叉轴均匀分布,位于首尾两端的行到父容器相切

stretch:拉伸对齐

4)6 大项目属性

以下 6 个属性设置在子项目上:

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

order 属性定义子项目的排列顺序,它会覆盖 HTML 结构中的顺序。默认值为 0 ,即遵循 HTML 结构排列;可以是负值,数值越小越靠前。

flex-grow 属性定义了父容器还有剩余空间时,子项目如何瓜分这些剩余空间

  • 其值为一个权重(扩张因子),子项目将按照设定的这个权重去瓜分父容器的剩余空间。

  • 如果为 0(默认):即使有剩余空间,子项目也不会去瓜分

  • 如果为整数,举个例子:
    父容器宽度 500px,三个子项目的 width 分别为 100px,150px,100px。
    于是剩余空间为 150px
    三个项目的 flex-grow 分别是 1,2,3,于是三个项目所得到的多余空间分别是:
    150 * 1 / 6 = 25px
    150 * 2 / 6 = 50px
    150 * 3 / 6 = 75px
    于是三个项目最终的宽度分别为:
    100px + 25px = 125px
    150px + 50px = 200px
    100px + 75px = 175px

  • 如果为小数,那么将不会计算权重之和作为权重率的分母,而是直接取 1 作为分母。在这个基础上,若权重之和小于 1 .则剩余空间不会全部分配给子项目。比如改一下上面的例子:
    三个项目的 flex-grow 改为 0.1,0.2,0.3,那么计算公式将变成下面这样:
    150 * 0.1 / 1 = 15px
    150 * 0.2 / 1 = 30px
    150 * 0.3 / 1 = 45px
    150px - 15px - 30px - 45px = 60px,可见还有 60px 没有分配给任何子项目。
    三个项目的最终宽度分别为:
    100px + 15px = 115px
    150px + 30px = 180px
    100px + 45px = 145px

注意:flex-grow 还会受到 max-width 的影响。如果最终 grow 后的结果大于 max-width 指定的值,则 max-width 的值将会优先使用。同样会导致父容器有部分剩余空间没有分配。

flex-shrink 属性定义了父容器空间不足时子项目如何收缩以适应有限的空间

该属性与 flex-grow 相对,不同的是其值的计算还与自身宽度有关。举个例子:
父容器 500px,三个子项目宽度分别为 150px,200px,300px,
flex-shrink 分别为 1,2,3。

首先,计算子元素溢出多少:150 + 200 + 300 - 500 = -150px。
那么这 -150px 将由三个元素分别收缩一定的量来弥补。

具体的计算方式为:每个元素收缩的权重为其 flex-shrink 乘以其宽度。

所以总权重为 1 * 150 + 2 * 200 + 3 * 300 = 1450

三个元素分别收缩:

1501(flexshrink)150(width)1450=15.5150*\frac{1(flex-shrink)*150(width)}{1450}=-15.5

1502(flexshrink)200(width)1450=41.4150*\frac{2(flex-shrink)*200(width)}{1450}=-41.4​​

1503(flexshrink)300(width)1450=93.1150*\frac{3(flex-shrink)*300(width)}{1450}=-93.1

三个元素的最终宽度分别为:
150 - 15.5 = 134.5
200 - 41.4 = 158.6
300 - 93.1 = 206.9

同样,当所有元素的 flex-shrink 之和小于 1 时,计算方式也会有所不同:
此时,并不会收缩所有的空间,而只会收缩 flex-shrink 之和相对于 1 的比例的空间。

还是上面的例子,但是 flex-shrink 分别改为 0.1,0.2,0.3。

于是总权重为 145(正好缩小 10 倍,略去计算公式)。

三个元素收缩总和并不是 150px,而是只会收缩 150px 的 (0.1 + 0.2 + 0.3) / 1 即 60% 的空间:90px。

每个元素收缩的空间为:

900.1(flexshrink)150(width)145=9.3190*\frac{0.1(flex-shrink)*150(width)}{145}=-9.31​​

900.2(flexshrink)200(width)145=24.8390*\frac{0.2(flex-shrink)*200(width)}{145}=-24.83​​​​

900.3(flexshrink)300(width)145=55.8690*\frac{0.3(flex-shrink)*300(width)}{145}=-55.86​​

三个元素的最终宽度分别为:
150 - 9.31 = 140.69
200 - 24.83 = 175.17
300 - 55.86 = 244.14

当然,flex-shrink 也会受到 min-width 的影响。

flex-basis 属性定义了子项目在不伸缩(即没有以上两个属性影响)时的原始尺寸,主轴水平时表示宽度,主轴垂直时表示高度。默认值为 auto。

以主轴水平为例,说一下子项目宽度如何决定:
简单地说,应用规则是:
content –> width –> flex-basis (limted by max|min-width)
也就是说,

  • 在显式指定 flex-basis 时,flex-basis 即为该值,width 被忽略;
  • 在没有显式指定 flex-basis 时,flex-basisauto,即采用 width 的值;
  • 在没有设置 width 的值时, flex-basis 采用项目内容的大小
  • flex-basis 始终无法小于指定的最小宽度,无法大于指定的最大宽度

flex是一个复合属性,值只有一个时等同于 flex-grow,值为三个时,等同于设置了 flex-grow,flex-shrink,flex-basis

虽然 flex 是多个属性的缩写,允许 1 - 3 个值连用,但通常用 1 个值就可以满足需求

align-self 属性单独定义了一个子项目在交叉轴方向上如何排列,它的可选值与 align-items 的可选值完全一致,两者同时设置时将优先考虑 align-self

2.2 历史版本

flex 在演化过程有三个版本:
2009 旧版本: display:box | display:inline-box
2011 混合版本: display:flexbox | display:inline-flexbox
2016 新版本: display: flex | display:inline-flex

旧版相对于新版的主要区别:flex 项目必须是 block,没有换行设置,没有反向设置,主轴没有 space-around,顺序值从 1 开始。当然,我们只了解新版 flex 就可以。

2.3 浏览器兼容性

2.4 总结

最后放一张属性总结的思维导图:

参考:
详解 flex-grow 与 flex-shrink
一劳永逸的搞定 flex 布局
Flex 布局教程:语法篇
flex basis 与 width 的区别
Flexbox Fundamentals