如何使用 Flex ?
作为 CSS3 推出的新一代的布局方案,掌握是必须的,Flex 究竟是什么?该如何使用,增加的对其方式又是如何?本文给你答案。
前言
作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how 每篇文章试图解释清楚一个问题。
这次的 why what or how 主题:如何使用 Flex ?
是什么?
Flex - 一种布局方式
在深入了解前,先来看看浏览器的支持情况吧:

一片绿色,那还等什么呢?赶紧掌握呀~
先了解了解布局的发展史,在了解历史的基础上在回头来看看 Flex ,以史为镜,可以知兴替嘛~
布局发展史
Table
作为 HTML 的老牌元素,Table 虽然作为表格存在,但是很长一段时间却被用来布局,大致的过程就是将设计稿按照表格切好,然后在表格响应位置放上内容。虽然笔者没有经历过这个阶段,但想想也是很复杂的。之前在学校的时候,学校的官网就是用表格来写的页面,不知道现在改了没有。
div + css
可以说这是目前大多数网站的布局方式,按照 css 的内容细分为以下 3 种方式
- 利用 
float的特性,配合margin实现左右布局 - 利用 
inline-block进行布局 - 利用 
position进行元素的定位 
但是,和 Table 一样,float inline-block 虽然可以用来布局,但 float 的出现却是为了解决图文混排,inline-block 是为了解决内联元素修改不了大小的问题,因此这俩虽然可以用来布局,但就像披着羊皮的狼,时不时给你的页面整个 bug 。这个时期为了解决这些问题,诞生了很多经典的布局方式,想要实现某种网页布局也需要特殊的文档结构,但这就是对的吗?
Flex
为了解决 Css 属性的误用(或者说是规范布局),HTML5 制定了一个与布局相关的属性:Flex。那么 Flex 解决了什么?又有什么好处,且听我慢慢道来。
布局
离开了 Dom 谈 Css 那就是瞎扯,那么假设有这样一个 DOM 结构:

使用 Flex 必须要有一个容器,在上面的结构中,黑色线框为容器,浅色内容为容器内容。假设黑色线框内为 div.parent,浅色内容为 div.child。当我们为容器设置以下样式时:
.parent {
    display: flex;
}
神奇的事情发生了,它变成了这样:

先来解释解释图上关键字所代表的内容
- 主轴,内容排列方向,默认从左向右。
 - 交叉轴,与内容排列方式垂直的方向,默认从上到下。
 - 容器宽(高),容器所占页面上的大小。
 - 主(交叉)轴起(终)点,代表主(交叉)轴的起始点。
 
记好这张图,我们接着来说说 Flex 涉及的属性。还有一点需要记住:当元素设置了 display: flex 后,其内的内容元素排列不遵循文档流规则,根据容器的主轴以及交叉轴排列。
语法
由于 Flex 涉及容器和内容两级元素,这两级元素分别有单独的属性,用于控制最终的呈现效果。先看看容器属性都有哪些。
容器属性
以下属性设置容器元素上。
flex-direction
| --- | --- | 
|---|---|
| 名 | flex-direction | 
| 值 | row | row-reverse | column | column-reverse | 
| 默认值 | row | 
| 含义 | 规定主轴方向 | 
| 值 | 含义 | 
|---|---|
row | 
内容块(主轴)从左向右排列 | 
row-reverse | 
内容块(主轴)从右向左排列 | 
column | 
内容块(主轴)从上向下排列 | 
column-reverse | 
内容块(主轴)从下到上排列 | 
图示:箭头代表主轴方向。
.parent {
  flex-direction: row | row-reverse | column | column-reverse;
}

flex-wrap
| --- | --- | 
|---|---|
| 名 | flex-wrap | 
| 值 | nowrap | wrap | wrap-reverse | 
| 默认值 | nowrap | 
| 含义 | 规定交叉轴的方向 | 
| 值 | 含义 | 
|---|---|
nowrap | 
不换行,容器为单行布局 | 
wrap | 
换行,容器为多行布局,主轴为左右时,交叉轴从上到下,主轴为上下时,交叉轴从左到右 | 
wrap-reverse | 
换行,容器为多行布局,交叉轴方向与 wrap 相反 | 
当属性设置为 nowrap 时,内容块单行排列。
当属性设置为 wrap/wrap-reverse 时,容器为多行布局,该属性也可以认为是单行和多行布局的区别属性,记住这个区别,会影响内容布局。
.parent {
  flex-wrap: nowrap | wrap | wrap-reverse;
}
图示:

对于 wrap-reverse 的表现可以这么理解:
如果说 flex-direction 修改了主轴方向,那么 flex-wrap 则修改了交叉轴的方向,当该属性值为 wrap-reverse,那么交叉轴方向为从下到上(或从右到左),内容在容器内按照主轴和交叉轴的方向排列,就会产生图示的效果。
flex-flow
该属性为 flex-direction 与 flex-wrap 的简写形式,语法定义如下
.parent {
  // flex-flow: <flex-direction> <flex-wrap>;
  flex-flow: row wrap;
}
之前说过,Flex 容器内部有属于自己的元素排列方式,该属性就定义了容器内元素的排列方式,flow 就有流的含义。
justify-content
| --- | --- | 
|---|---|
| 名 | justify-content | 
| 值 | flex-start | flex-end | center | space-between | space-around | space-evenly | 
| 默认值 | flex-start | 
| 含义 | 规定内容在主轴上的呈现效果 | 
结合之前的图片,我们来分析分析

解释这些属性的具体含义前,我们需要确定主轴以及交叉轴的方向,经过上面的介绍相信大家也应该了解这图对应的 flex-flow 为 row nowrap,其他的 flex-flow 对应其他的图,这点需要清楚。
| 值 | 含义 | 
|---|---|
flex-start | 
内容块往主轴起点挤。 | 
flex-end | 
内容块往主轴终点挤。 | 
center | 
内容块往中间挤,容器主轴两边剩余空间相等。 | 
space-between | 
内容块两端对齐,内容块主轴方向上间距相等,容器主轴两端无剩余空间。 | 
space-around | 
将容器主轴剩余空间均分到每块内容的两侧,内容块间距为容器主轴两端剩余空间的两倍。 | 
space-evenly | 
均分主轴空间,内容块间距与容器主轴两端剩余空间相等。 | 
有点绕口,看图就能理解了,箭头上的数字代表该容器内各段空间的比例。

align-content
| --- | --- | 
|---|---|
| 名 | align-content | 
| 值 | flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | 
| 默认值 | stretch | 
| 含义 | 规定容器每行的呈现效果 | 
之前做 flex-wrap 介绍时,提到过,该属性决定了容器是否为多行显示,当内容块多行显示时,align-content 决定了容器每行的呈现效果,为了方便起见,由内容块组成的行,定义一个名字:容器行。容器行的高度取决于该行元素的最高内容。
| 值 | 含义 | 
|---|---|
flex-start | 
容器行往交叉轴起点挤。 | 
flex-end | 
容器行往交叉轴终点挤。 | 
center | 
容器行往交叉轴中间挤,容器交叉轴两边剩余空间相等。 | 
space-between | 
容器行两端对齐,容器行交叉轴方向上距离相等,容器交叉轴两端无剩余空间。 | 
space-around | 
将容器交叉轴剩余空间均分到每个容器行两侧,容器行间距为容器交叉轴两端剩余空间的两倍。 | 
space-evenly | 
均分交叉轴空间,内容块间距与容器交叉轴两端剩余空间相等。 | 
stretch | 
将交叉轴剩余空间给均分给容器行,需要注意的是该值将直接改变容器行的高度,而不是改变容器行的呈现位置。 | 
该属性的值与 justify-content 的属性很像,仅仅多了一个 stretch 。但需要注意的是,该值是默认值。
字面的意思不容易理解,对比图中效果就 ok 了,箭头上的数字同样为比例,红色线框为容器中容器行所在的位置。

介绍该属性时说过,该属性定义容器行的呈现效果,那么该属性在 flex-wrap 为 nowrap 时,有用吗?
没用! 但是虽然 flex-wrap 为 nowrap 不存在多行,但是可以简单的理解为容器行就为容器,也就是 justify-content 为 stretch 的效果,并且不会被改变,也就是说即使设置了 justify-content 为其他值,但容器还是呈现出 stretch 的效果。
那么如果 flex-wrap 为 wrap/wrap-reserve,但内容块仅有一行时,有用吗?完全有用,且符合预期。
因此该属性生效的前提就是 flex-wrap: wrap/wrap-reserve。
align-items
| --- | --- | 
|---|---|
| 名 | align-items | 
| 值 | flex-start | flex-end | center | baseline | stretch | 
| 默认值 | stretch | 
| 含义 | 规定内容在每一个容器行中的呈现效果 | 
| 值 | 含义 | 
|---|---|
flex-start | 
内容块在该容器行中往主轴开始方向挤。 | 
flex-end | 
内容块在该容器行中往主轴结束方向挤。 | 
center | 
内容块在该容器行中上下居中。 | 
baseline | 
该容器行中所有内容块的基线在同一交叉轴上。 | 
stretch | 
内容块在主轴方式上撑满该容器行。如果内容块设置了交叉轴方向上的大小,则效果与 flex-start 一致。 | 
在平常的使用中,其实 align-content 是很少用到的,但是为什么要在 align-items 前说明,是因为 align-items 的呈现效果与容器行相关,这里再次着重说一下
以主轴左右方向为例,若主轴方向为上下,将说明中的高度换为宽度即可
- 如果容器为多行(设置了 
flex-wrap: wrap/wrap-reserve),容器行的高度由每行中最高元素确定。 - 如果容器为单行(设置了 
flex-wrap: nowrap),那么该行高度为Math.max(容器高度, 该行中最高的元素的高度)。不管有没有设置align-content。 - 容器是否为多行并非由呈现效果决定,仅由 
flex-wrap确定,即使设置了换行但内容不够换行也为多行。 
图示:因为该属性的表现仅与容器行相关,这里以单行容器作为示例。为了展示出 baseline\stretch 的效果,例子用 line-height padding-top 撑起内容高度。

- 图中红线为每块内容基线对其后的基线位置,对齐后由基线上最高的元素顶离容器行的交叉轴起点。
 - 图中最后容器中第二块内容高度超出了容器高度,那么容器行高度就为最高的内容的高度。
 
内容属性
以下内容设置在内容元素上。
order
| --- | --- | 
|---|---|
| 名 | order | 
| 值 | <number> | 
| 默认值 | 0 | 
| 含义 | 规定内容块的排列顺序 | 
内容块的排列顺序由该属性决定,order 大的内容块优先排列,如果 order 一样,文档位置靠前的内容块优先排列。
经过上面的内容相信大家知道了,内容块的排列方式由容器元素的主轴与交叉轴决定,那么排列顺序就由该属性决定了。
图示:元素内的数字为该元素的 order

flex-basis
| --- | --- | 
|---|---|
| 名 | flex-basis | 
| 值 | <length> | auto | 
| 默认值 | auto | 
| 含义 | 设置内容块的基础大小 | 
定义内容块的基础大小,为何是基础大小呢?这和后面介绍的两个属性有关。内容块的基础大小表现如下:以主轴方向左右为例
- 当该值为 
auto或未设置时,内容块的基础宽度由内容块内部决定,由margin + border + padding + content撑开内容块,表现出的效果和inline-block一致。 - 当该值为具体长度时,根据 
box-sizing分为两种情况:box-sizing为content-box时,相当于设置了内容的content大小。box-sizing为border-box时,相当于固定了内容的border + padding + content的大小。
 
这里问个问题:设置了 flex-basis 后,内容块的在主轴方向上的大小是否就固定了?
错!! 要记住这个点,内容块的大小永远由 margin + border + padding + content 所决定(也就是盒模型),加了 flex-basis 仅仅做了某几个值限定而已,这点需要记好。
flex-grow
| --- | --- | 
|---|---|
| 名 | flex-grow | 
| 值 | <number> | 
| 默认值 | 0 | 
| 含义 | 设置内容块的扩大比例 | 
默认值为 0,并且当该值小于 0 时,相当于 0。
当内容块在容器行中排列完并且有剩余空间时,该值规定了剩余空间该如何并入内容块中。具体的过程如下
- 确定容器剩余空间,假设为 
10px。 - 计算出该容器行内所有内容块的 
flex-grow总和,假设该行中仅有两内容块,分别为2和3。 - 计算出每一份对应的宽度,
10 / (2 + 3) = 2 - 第一份内容块的 
content大小加2 * 2 = 4 - 第二份内容块的 
content大小加3 * 2 = 6 
需要注意的是:增加的大小会加到内容块的 content 上,并不会去改变内容块的 margin border padding。
flex-shrink
| --- | --- | 
|---|---|
| 名 | flex-shrink | 
| 值 | <number> | 
| 默认值 | 1 | 
| 含义 | 设置内容块的缩小比例 | 
默认值为 0,并且当该值小于 0 时,相当于 0。
当内容块在容器行中排列结束并超出容器时,该值规定了元素该如何缩小,让元素尽量不超出容器。
需要注意的是,当容器设置了换行后,由于内容块永远都不会超出容器,因此该值在单行容器中生效。
缩小过程如下:
- 确定内容块排列后超出的容器空间,假设为 
10px。 - 计算出该容器行内所有内容块的 
flex-shrink总和,假设该行中仅有两内容块,分别为2和3。 - 计算出每一份对应的宽度,
10 / (2 + 3) = 2。 - 第一份内容块的 
content大小减2 * 2 = 4。 - 第二份内容块的 
content大小减3 * 2 = 6。 
需要注意的是,该过程仅是在理想状态下的过程,有可能内容块的大小可能不够减,那么该内容块会尽量把其中的内容显示出来,而内容块依然会超出容器。
flex-grow 与 flex-shrink 会修改内容块的默认大小,也就是 flex-basis 设定的值,也就是说内容块的大小由这 3 个值所确定。
flex
| --- | --- | 
|---|---|
| 名 | flex | 
| 值 | none | auto | <length> | [ <flex-grow> <flex-shrink> <flex-basis> ] | 
| 默认值 | 1 | 
| 含义 | 为 flex-grow flex-shrink flex-basis 的复合属性 | 
| 值 | 含义 | 
|---|---|
none | 
等效于 0 0 auto | 
auto | 
等效于 1 1 auto | 
length | 
等效于 1 1 length | 
align-self
| --- | --- | 
|---|---|
| 名 | align-self | 
| 值 | auto | flex-start | flex-end | center | baseline | stretch | 
| 默认值 | auto | 
| 含义 | 规定该内容块在所在容器行的呈现效果 | 
该值属性和容器的 align-items 表达的效果一致,auto 为使用 align-items 所规定的值,若设置了其他值,则容器所规定的 align-items 在该内容块上无效。
图示:在每一个容器的第二个内容块上设置了 align-self: flex-start

ok 到此与 Flex 相关的 12 属性已解释完毕,由于该属性作为一个布局属性,相信在 HTML 中用的还是很多的,因此在说说应用吧。
应用
该节为在实际项目中的经验之谈,如有不得当的地方欢迎指出。
在 scss 中,我经常会有如下定义
.x-flex {
  display: flex;
  &.x-m-l2r {
    flex-direction: row;
  }
  &.x-m-r2l {
    flex-direction: row-reverse;
  }
  &.x-m-t2b {
    flex-direction: column;
  }
  &.x-m-b2t {
    flex-direction: column-reverse;
  }
}
很明显,只要设置了 x-flex class 就能有一个 Flex 容器,m 代表主轴,l2r 代表主轴方向,从左到右。这样我们就很容易定义一个单行的 Flex 容器,方向仅需要在加一个对应的类名即可。
那多行容器呢?仅需在加一个交叉轴的定义即可,代码如下
.x-flex {
  display: flex;
  // ...
  &.x-a-t2b, &.x-a-l2r {
    flex-wrap: wrap;
  }
  &.x-a-b2t, &x-a-r2l {
    flex-wrap: wrap-reverse;
  }
}
a 代表交叉轴。
经过前面的反复强调,相信大家对于:容器的多列布局由 flex-wrap 开启,开启后由 flex-wrap 确定容器交叉轴的方向。已经清楚了。
有了上诉两段代码,容器的 flow(内容块布局方式)也就可以确定了。
接着我们结合内容块在容器行中的呈现,一般我们都是需要居中呈现的,那么就可以得到以下代码
.x-flex {
  display: flex;
  // ...
  align-items: center;
  justify-content: center;
  &.x-m-start {
    justify-content: flex-start;
  }
  &.x-m-end {
    justify-content: flex-end;
  }
  // ... 类似定义
  &x-a-start {
    align-items: flex-start;
  }
  // ... 类似定义
}
其默认值为居中呈现,x-m-name 代表主轴方向上的空间分配,x-a-name 代表交叉轴方向上的空间分配。
那么容器行呢?同理(首先要确保理解了容器行的概念,没理解可以返回在看一遍)
.x-flex {
  display: flex;
  // ...
  align-content: center;
  &.x-r-start {
    align-content: flex-start;
  }
  &.x-r-end {
    align-content: flex-end;
  }
  // ... 类似定义
}
r 代表容器行相关定义,x-r-name 代表交叉轴上容器行的空间分配。
这样基本上所有容器相关的概念就都转换为语义上的表达了,这里列几个常用容器
| 布局 | 内容块左右布局 | 内容块上下布局 | className | 
|---|---|---|---|
| 单行从左到右 | 居中 | 居中 | x-flex | 
| 单行从右到左 | 居右 | 居上 | x-flex x-m-r2l x-m-start x-a-start | 
| 多行主轴右左交叉轴下上 | 居右 | 居下 | x-flex x-m-r2l x-a-b2t x-m-start x-a-start | 
| 多行左右下上,容器行居上 | 居右 | 居下 | x-flex x-a-b2t x-m-end x-a-start | 
| ... | ... | ... | ... | 
当然这避免不了在 class 上写过多的类名,那我们先发散一下思维,既然 Flex 是一种结构,那么直接放在 HTML 标签上可以吗?
当然可以,这里抛砖引玉一下:
[flex] {
  display: flex;
  align-items: center;
  justify-content: center;
  align-content: center;
  &[f-m=l2r]{
    flex-direction: row;
  }
  // ...
}
那么 HTML 就可以这么写
<div class="parent" flex f-m="l2r">
  <div class="child">1</div>
  <div class="child">2</div>
  <div class="child">3</div>
  <div class="child">4</div>
</div>
然后将该份 CSS 保存在 CDN 上,该份文件很小不会被改变,且可以在不影响 class 使用的情况下直接定义文档结构,是不是美滋滋啊 ~
总结
本文讨论了 Flex,包括它的原理,使用技巧等。最后在强调一下,好好理解下容器行的概念,这点在多行布局中至关重要,也只有理解了这个概念,Flex 使用起来才能得心应手,当然你也可以把 Flex 当成是一个用于元素绝对居中的样式属性,但希望在看了这篇文章之后,对 Flex 有一个新的看法,仅仅用来绝对居中实在是大材小用了。
照例,问几个问题
Flex容器都有哪些属性?Flex内容块都有哪些属性?- 那几个属性会导致内容块发生大小变化?有 
4个 - 容器行是什么?
 
参考
最后的最后
该系列所有问题由 minimo 提出,爱你哟~~~