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 布局以后,子元素的 float
、clear
和 vertical-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-direction
和flex-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
三个元素分别收缩:
三个元素的最终宽度分别为:
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。
每个元素收缩的空间为:
三个元素的最终宽度分别为:
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-basis
为auto
,即采用 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