Flex 布局,也称弹性布局,是相对于浮动布局来说更好的一种布局方式,但并不算是一种新的布局方式了,基本上在做移动端开发的时候都在用,而 PC 端使用得相对较少,因为要兼容老年浏览器。
我在平时开发的过程中也会使用,只是发现对有些属性似乎了解得不多,所以这篇文章就来全面的复习一下 Flex 布局。
container
container 就是采用 Flex 布局的元素,简称为容器,其内部的子元素叫 items,简称为子项(某些教程中翻译为项目,我个人觉得拗口)。
容器有两条轴,主轴(main axis)和交叉轴(cross axis),这两条轴的方向是不固定的,并不是有些教程中说的水平方向是主轴、垂直方向是交叉轴,这点一定要注意,稍后再做详细解释。
两条轴对应的就有两对(四条)边线,主轴和交叉轴上各自都有的起始线(start line)和终止线(end line),如果觉得没有概念,类比一下矩形的边框就明白了。之所以提这两条线,是因为这两条线也是有用处的,稍微再做详细解释。
需要注意的是四条边线的 start 和 end 也是不固定的,会受到 writing-mode
的影响,文中的示例都没有修改 writing-mode
。
display
让一个元素使用 Flex 布局的方法就是设置其 display
为 flex
或 inline-flex
,两者略有不同。
- flex
当设置 display
为 flex
时,容器元素自身是块级的,也就是说其宽度会充满父级元素宽度,这里就不展开了,请查看后面的示例。
- inline-flex
当设置 display
为 inline-flex
时,容器元素自身是行内级的,也就是说其宽度为内容宽度之和,这在有些情况下是非常便捷的一种布局方式。
举个例子,ul
和 ol
元素默认是带有项目符号的,但是默认的项目符号并不是很好看。
如果有个需求是需要将项目符号修改成设计师所设计的样子,在不考虑兼容性的情况下,可以使用伪元素 ::marker
来实现;但如果考虑兼容性问题,我们第一时间想到的方案就是去掉原有的项目符号,在前面放置一个元素来进行模拟。
无论采用哪种方案,都会涉及到一个问题,那就是当项目符号设计图尺寸和后续内容的尺寸不一致时,如何能够更简单的实现对齐。
inline-flex
就能很好的解决这个问题:
#test {
margin: 0 auto;
width: 500px;
outline: dashed 1px red;
padding: 10px;
}
.flex-inline {
height: 30px;
display: inline-flex;
outline: dashed 1px black;
padding: 5px 10px;
align-items: center;
}
.symbol {
width: 30px;
height: 30px;
background: url("./symbol.png") no-repeat center;
background-size: cover;
margin-right: 10px;
}
将容器的 display
设为 inline-flex
,同时将 align-items
设为 center
,交叉轴方向居中,主轴方向采用默认排列方式,下面是 html 代码:
<div id="test">
<div class="flex-inline">
<div class="symbol"></div>
<span>我是一句长长的话</span>
</div>
</div>
得到结果如下:
可以看到,项目符号和文字在交叉轴方向上是居中的。
如果将容器的 display
设为 flex
:
.flex-inline {
height: 30px;
display: flex;
outline: dashed 1px black;
padding: 5px 10px;
align-items: center;
}
得到的结果如下:
仔细观察就会发现,不同点就在于容器的宽度,也就是黑色虚线框的宽度。
flex-direction
flex-direction
用于改变主轴的方向,前面提过主轴和交叉轴的方向是不固定的原因就在这里。
flex-direction
的可选值有四个,分别是 row
、row-reverse
、column
和 column-reverse
。
- row
row
是默认值,当没有设置容器的 flex-direction
时,默认使用此值,表示主轴为水平方向(或行方向),子项排列顺序为从左往右。
需要注意的是子项的排列顺序还会受到 direction
的影响,继续使用上面的例子,我们将容器的 direction
设为 rtl
,
.flex-inline {
height: 30px;
display: inline-flex;
outline: dashed 1px black;
padding: 5px 10px;
align-items: center;
direction: rtl;
}
得到的结果如下:
可以看到项目符号和文字的位置“交换”了。
在接下面的内容中我们都假设 direction
为默认值 ltr
,方便简述。
- row-reverse
row-reverse
顾名思义,表示主轴依然为水平方向(或行方向),但是子项排列顺序反向。
继续使用上面的例子,我们将容器的 flex-direction
设置为 row-reverse
:
.flex-inline {
height: 30px;
display: inline-flex;
outline: dashed 1px black;
padding: 5px 10px;
align-items: center;
flex-direction: row-reverse;
}
得到的结果会和上一个结果一样:
- column
column
会改变的主轴方向为垂直方向(或列方向),效果如下:
.flex-inline {
height: 30px;
display: inline-flex;
outline: dashed 1px black;
padding: 5px 10px;
align-items: center;
flex-direction: column;
}
可以看到项目符号和文字变成了上下排列,这里之所以看不全项目符号,是因为我们将容器的 height
设为了 30px
,容器会忽略子项在主轴方向上的 width
或 height
,而选择尽可能的将所有子项限制在自身尺寸范围内,进而对子项的 width
或 height
进行压缩(未设置 flex-wrap
)。
看到这里如果还是不太明白为什么说会改变主轴方向的话,我们再修改一个参数,将容器的高度设置为 200px
:
.flex-inline {
height: 200px;
display: inline-flex;
outline: dashed 1px black;
padding: 5px 10px;
align-items: center;
flex-direction: column;
}
得到的结果如下:
- column-reverse
column-reverse
和 column
都是将容器主轴修改为垂直方向(或列方向),只是子项的排列顺序反向:
.flex-inline {
height: 200px;
display: inline-flex;
outline: dashed 1px black;
padding: 5px 10px;
align-items: center;
flex-direction: column-reverse;
}
结果如下:
flex-wrap
flex-wrap
用于设置当子项正常宽度(margin
和 padding
也要算上)之和大于容器宽度时是否换行,以及如何换行。
- nowrap
nowrap
表示不换行,是 flex-wrap
的默认值,为了能更好的看到效果,我们换个例子:
html 结构如下:
<div id="test">
<div class="flex">
<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 class="flex-item">9</div>
<div class="flex-item">10</div>
<div class="flex-item">11</div>
<div class="flex-item">12</div>
</div>
</div>
css 代码如下:
#test {
margin: 0 auto;
width: 500px;
outline: dashed 1px red;
padding: 10px;
}
.flex {
display: flex;
width: 300px;
outline: dashed 1px black;
}
.flex-item {
width: 60px;
height: 60px;
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
line-height: 60px;
text-align: center;
}
结果如下:
黑色虚线框为容器,可以看到为了尽可能显示所有子项而对子项进行了压缩。
我们对其中的一些子项增加一些内容:
<div id="test">
<div class="flex">
<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">66666666</div>
<div class="flex-item">7</div>
<div class="flex-item">8</div>
<div class="flex-item">9</div>
<div class="flex-item">10</div>
<div class="flex-item">11</div>
<div class="flex-item">12222222</div>
</div>
</div>
在没有设置 flex-wrap
时,如果压缩之后依旧显示不下,则超出部分会被隐藏,但是在不同浏览器上的表现略有不同(都未对容器设置 overflow
):
这是 Chrome 上的效果:
这是 Firefox 上的效果:
事实上我们也并不用太在意这之间的差异,因为绝大多数情况下我们都不太可能会需要这种效果。
需要注意的是,之前说过会被忽略的 width
或 height
只是主轴方向上的,我们来验证一下,选中标号为 11 的元素:
可以看到,其 width
只有 38.41px
,但是 height
依然是 60px
,说明 height
并未被压缩。
将容器的 flex-direction
修改为 column
:
.flex {
display: flex;
width: 300px;
outline: dashed 1px black;
height: 300px;
flex-direction: column;
}
.flex-item {
width: 60px;
height: 60px;
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
/*line-height: 60px;*/
text-align: center;
}
效果如下:
可以看到,这次被压缩的就是 height
了。
- wrap
wrap
表示当子项宽度(或高度)之和过大时换行,即不对子项进行压缩。
修改容器的 flex-wrap
为 wrap
:
.flex {
display: flex;
width: 300px;
outline: dashed 1px black;
flex-wrap: wrap;
}
.flex-item {
width: 60px;
height: 60px;
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
line-height: 60px;
text-align: center;
}
得到的结果如下:
这里并没有对容器的高度进行限制,所以是自动撑开的,可以看到现在显示了三行。
- wrap-reverse
wrap-reverse
和 wrap
一样,也是表示换行,不同的是子项的行顺序反向。
修改容器 flex-wrap
值为 wrap-reverse
:
.flex {
display: flex;
width: 300px;
outline: dashed 1px black;
flex-wrap: wrap-reverse;
}
.flex-item {
width: 60px;
height: 60px;
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
line-height: 60px;
text-align: center;
}
得到的结果如下:
可以看到,子项的行顺序反向了。
当容器的 flex-direction
为 column
或 column-reverse
时也是一样的效果,大家可以自行试验。
flex-flow
flex-flow
是 flex-direction
和 flex-wrap
的简写,默认值为 row wrap
。
我们可以用它来试验一下上面没有做的效果:
.flex {
display: flex;
width: 300px;
height: 300px;
outline: dashed 1px black;
/*flex-wrap: wrap-reverse;*/
flex-flow: column wrap-reverse;
}
得到的结果如下:
可以看到,表现是一样的。
有一点提一下,从效果上看行与行之间似乎有了间距,这个稍后再解释。
justify-content
justify-content
用于设置子项在主轴方向上的排列方式,有 10 个值可以设置。
- flex-start
默认值,它表示子项在主轴方向上从起始线开始排列。
- start
start
和 flex-start
相比就只少了一个前缀,但意义完全不同,它表示的是从 writing-mode
设置的方向的起始位置开始排列。
继续上面的例子,删除部分子项,只留下 4 个,将容器的 justify-content
设为 start
,同时将容器的 writing-mode
设为 vertical-rl
:
.flex {
display: flex;
width: 300px;
height: 300px;
outline: dashed 1px black;
justify-content: start;
writing-mode: vertical-rl;
}
得到的结果如下:
这就是前面说过的 writing-mode
的影响之一,关于 writing-mode
的更多信息,可以查阅相关文档。
- flex-end
表示子项在主轴方向上从终止线开始排列。
- end
此属性与 start
方向刚好相反。
- center
表示子项在主轴方向上居中,多出的空间均匀分布在所有子项的两侧。
- space-between
多出的空间均匀分布到每个子项与子项之间。
- space-around
多出的空间均匀分布到每个子项两则,即子项与主轴的起始线或终止线之间也是有距离的。
- space-evenly
多出的空间均匀环绕每个子项,一定要仔细观察与 space_around
的差别,前者子项与子项的间距是子项与主轴的起始线或终止线的间距的两倍。
align-items
此属性用于设置子项在交叉轴上的对齐方式,有 9 个值可以设置。
- stretch
默认值,表示子项在交叉轴方向上撑满容器高度,但是依然会受到 height
、min-height
和 max-height
的约束。
在前面的例子中,我们看到的子项高度并没有撑满容器高度,因为我们给子项设置了 height
为 60px
,将子项的高度去掉:
.flex-item {
width: 60px;
/*height: 60px;*/
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
line-height: 60px;
text-align: center;
}
得到的结果如下:
可以看到子项的高度撑满了容器高度。
- flex-start
表现与 justify-content:flex-start
类似,不同的是在交叉轴方向上。
- start
表现与 justify-content:start
类似,不同的是在交叉轴方向上。
- flex-end
表现与 justify-content:flex-end
类似,不同的是在交叉轴方向上。
- end
表现与 justify-content:end
类似,不同的是在交叉轴方向上。
- center
表现与 justify-content:center
类似,不同的是在交叉轴方向上。
- baseline
以子项第一行文本的基线为基准进行排列,继续上面的列子,将容器的 align-items
设为 baseline
,并将第一个子项的 font-size
修改为 14px
:
.flex {
display: flex;
width: 300px;
height: 300px;
outline: dashed 1px black;
justify-content: space-evenly;
align-items: baseline;
}
同时给 html 做些修改:
<div id="test">
<div class="flex">
<div class="flex-item" style="font-size: 14px">1<br/>增加的文字</div>
<div class="flex-item">2</div>
<div class="flex-item">3</div>
<div class="flex-item">4</div>
</div>
</div>
得到的结果如下:
可以看到第一个子项的上方出现了间距。
align-content
此属性只有在出现了需要换行的情况下才有效,它的作用是设置当交叉轴方向上有空余空间时如何排列子项,表现和 justify-content
类似。一定要注意它与 align-items
的区别,它控制的是剩余空间的分布,而 align-items
控制的是子项在交叉轴上的对齐方式。
- flex-start
表现与 justify-content:flex-start
类似,不同的是在交叉轴方向上。
- start
表现与 justify-content:start
类似,不同的是在交叉轴方向上。
- flex-end
表现与 justify-content:flex-end
类似,不同的是在交叉轴方向上。
- end
表现与 justify-content:end
类似,不同的是在交叉轴方向上。
- center
表现与 justify-content:center
类似,不同的是在交叉轴方向上。
- space-between
表现与 justify-content:space-between
类似,不同的是在交叉轴方向上。
- space-around
表现与 justify-content:space-around
类似,不同的是在交叉轴方向上。
- space-evenly
表现与 justify-content:space-evenly
类似,不同的是在交叉轴方向上。
- stretch
默认值,表现与 align-items
为 stretch
时类似,都是撑满容器空间。
items
前面其实已经说过了 items 的含义,它表示的是容器内部的子元素,我称之为子项。
在容器控制子项排列的同时,我们也可以在一定程度上单独设置某子项的排列方式。
order
此属性用于设置某子项在所有子项中的排列顺序,默认情况所有子项的 order
值都为 0,因此我们在 html 中的书写顺序就是子项的排列顺序,继续使用上面的例子,我们将 3 号子项的 order
修改为 1:
<div id="test">
<div class="flex">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item" style="order:1">3</div>
<div class="flex-item">4</div>
<div class="flex-item">5</div>
<div class="flex-item">6</div>
</div>
</div>
得到的结果如下:
可以看到,3 号子项排到了所有子项的最后。
值得一提的是,值的类型必须是整数,但正数或负数都可以。
flex-grow
此属性可以设置某子项在主轴方向上与其他子项对剩余空间的占有比例,即子项在占有剩余空间时的拉伸比例,值不能为负数。
继续使用上面的例子,将子项的 flex-grow
设为 1:
.flex-item {
width: 60px;
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
line-height: 60px;
text-align: center;
flex-grow: 1;
}
将 3 号子项的 flex-grow
设为 2:
<div id="test">
<div class="flex">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item" style="flex-grow: 2">3</div>
<div class="flex-item">4</div>
<div class="flex-item">5</div>
<div class="flex-item">6</div>
</div>
</div>
得到的结果如下:
看上去除了 6 号子项占满了容器宽度外,其余子项并没有什么变化。这是因为 6 号子项所在的行只有它自身一个,而我们对其设置的 flex-grow
值为 1,所以它占满了容器宽度。
而 3 号子项我们设置的 flex-grow
值为 2,按理来说它所占的剩余空间宽度应是其他子项的两倍(一定要注意 flex-grow
指的是对剩余空间的占有比例),但似乎没有生效,那是因为所有子项按 flex-grow
为 1 进行排列时就已经超过容器宽度了,即使除去 6 号子项,其余子项也已经占满了容器宽度,所以 3 号子项无法拉伸了。
接着前面的例子,我们删除 5 号和 6 号子项:
<div id="test">
<div class="flex">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item" style="flex-grow: 2">3</div>
<div class="flex-item">4</div>
<!--<div class="flex-item">5</div>-->
<!--<div class="flex-item">6</div>-->
</div>
</div>
得到的结果如下:
可以看到 3 号子项比其他子项宽了,需要注意的是这种排列方式是在每一行单独进行的。
flex-shrink
此属性的作用恰好与 flex-grow
相反,flex-grow
是拉伸,而此属性是在剩余空间不足的情况下对子项的压缩比例。
继续上面的例子,先去掉容器元素的 flex-wrap
,然后将子项的 flex-shrink
的值都设为 1:
.flex {
display: flex;
width: 300px;
height: 300px;
outline: dashed 1px black;
/*flex-wrap: wrap;*/
justify-content: space-evenly;
align-content: flex-start;
}
.flex-item {
width: 60px;
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
line-height: 60px;
text-align: center;
flex-shrink: 1;
}
再将 3 号子项的 flex-shrink
的值设为 6:
<div id="test">
<div class="flex">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item" style="flex-shrink: 6">3</div>
<div class="flex-item">4</div>
<div class="flex-item">5</div>
<div class="flex-item">6</div>
</div>
</div>
得到的结果如下:
可以看到 3 号明显比其他子项窄得多。
再提一下,使用此属性时容器不能将 flex-wrap
设为 nowrap
以外的值,否则此属性无效;另外,复数也是不允许的。
flex-basis
此属性用于设置子项在排列时的宽度,可以是百分比,表示占容器宽度的比例,也可以是 px
或 rem
等单位的值,最大宽度为其余子项排列后的容器剩余宽度,最小宽度为子项内容宽度。
继续使用上面的例子,修改子项的样式内容:
.flex-item {
/*width: 60px;*/
outline: dashed 1px #ccc;
background-color: orange;
color: white;
font-size: 32px;
line-height: 60px;
text-align: center;
/*flex-shrink: 1;*/
}
设置 3 号子项的 flex-basis
为 50%
:
<div id="test">
<div class="flex">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item" style="flex-basis: 50%">3</div>
<div class="flex-item">4</div>
<div class="flex-item">5</div>
<div class="flex-item">6</div>
</div>
</div>
得到的结果如下:
可以看到 3 号子项的宽度正好的是容器宽度的一半。
flex
此属性是 flex-grow
、flex-shrink
和 flex-basis
组合的简写形式,这里就不再赘述。
align-self
此属性可以单独设置某个子项在交叉轴方向上的对齐方式,即覆盖容器元素设置的对齐方式,有多个值可选,参考 align-items
。
继续使用上面的例子,去掉容器元素的 flex-wrap
,并设置 align-items
为 center
:
.flex {
display: flex;
width: 300px;
height: 300px;
outline: dashed 1px black;
/*flex-wrap: wrap;*/
justify-content: space-evenly;
align-content: flex-start;
align-items: center;
}
将 3 号 元素的 align-self
设为 flex-end
:
<div id="test">
<div class="flex">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item" style="align-self: flex-end">3</div>
<div class="flex-item">4</div>
<div class="flex-item">5</div>
<div class="flex-item">6</div>
</div>
</div>
得到的结果如下:
可以看到 3 号元素在底部,而容器元素设置的是居中对齐,说明子项覆盖了容器元素的设定。
顺带提一句,float
、clear
和 vertical-align
对子项不起作用。
结语
Flex 布局本身并不是很复杂,只要搞清楚了不同属性的功能就能很好的掌握,多试验、不时复习有时会发现一些意想不到的东西。