文章介绍了 6 种 Web 常用的 Flexbox 布局:水平垂直居中,可通过设置容器属性实现单行或多行居中;等高布局,使用 Flexbox 实现更简单;均分列布局,相较传统方式,Flexbox 无需考虑诸多参数;圣杯布局,有特定的 HTML 结构和能力要求,可结合媒体查询实现响应式;12 列网格布局,比浮动构建简单;九宫格布局,给出了 HTML 和 CSS 关键代码。
水平垂直居中
Flexbox 中,可以在 Flex 容器上使用:
justify-content
的center
让 Flex 项目水平居中。- 对于单行而言,可以使用
align-items
的center
让 Flex 项目垂直居中;当然,使用align-content
的center
也可以让 Flex 项目垂直居中,但需要显式设置flex-wrap
的值为wrap
或wrap-reverse
。 - 对于多行而言(
flex-direction
显式设置了column
),则使用align-items
的center
让所有 Flex 项目水平居中,再配合justify-content
的center
实现垂直方向居中
在 CSS 中,我们可以像下面这样编码,可以轻易实现水平垂直居中:
适合用于居中对齐。
<div class="container">
<div class="centered">内容</div>
</div>
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 使容器占满整个视口高度,用于演示 */
}
.centered {
/* 其他样式 */
}
单行或多行实现水平垂直居中,两者都可以在 Flex 容器上设置 justify-content
和 align-items
属性的值为 center
,不同的是,在多行的 Flex 容器上要显式设置 flex-direction
属性的值为 column
。
你还可以将 justify-content
和 align-self
配合起来实现水平垂直居中的效果:
- Flex 容器上设置
justify-content
的值为center
,让 Flex 项目实现水平居中; - Flex 项目上(需要实现垂直居中的 Flex 项目)上设置
align-self
的值为center
。
比如:
<div class="container">
<div class="item">(^_^)</div>
<div>
.container {
display: flex; /* 或 inline-flex */
justify-content: center; /* Flex 项目水平居中 */
}
.item {
align-self: center;
}
意,从效果上可以看得出来,如果在 Flex 项目上未显式设置
align-self:center
时,Flex 项目会被拉伸,这是因为 Flex 容器的 align-items
默认值为 stretch
。如果你需要避免 Flex 项目在侧轴被拉伸,可以重置 align-items
的值为 stretch
之外的值。
这种方法也同样适用于多行或多列水平垂直居中:
.container {
display: flex;
justify-content: center;
}
.container > * {
align-self: center;
}
.container--multiple {
flex-direction: column;
}
等高布局
Web 设计师为了让页面或组件的 UI 更美观,往往会考虑像等高布局这样的效果:
如上图所示,右侧等高布局看起来总是要比左侧的不等高布局更舒服一些。虽然等高布局在 UI 上会令人感到更舒服,但在以往的布局技术中要实现等高布局还是有点麻烦的。
主要原因是我们并不知道元素的高度是多少 ,即使知道了,如果给元素上直接设置一个 height
值,很有可能就会造成内容溢出容器,甚至是打破 Web 布局。那么,使用 Flexbox (包括后面要介绍的 Grid)布局技术,实现等高布局就会轻易地多,甚至可以说是没有任何难度可言。比如,我们要实现一个等高布局的卡片组件:
果使用 Flexbox 布局技术,实现起来就很简单:
<div class="cards">
<div class="card">
<figure>
<img src="thumb.png" alt="缩略图" />
</figure>
<h3>Card Title</h3>
<p>Card Describe</p>
<button>Button</button>
</div>
<!-- 其他 Card -->
</div>
与布局相关的 CSS 代码:
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card {
display: flex;
flex-direction: column;
flex: 1 1 300px;
}
在这个示例中,卡片.card
和其容器 .cards
都是 Flex 容器,并且每张卡片的初始化尺寸是 300px
(即 flex-basis
的值为 300px
),而且容器 .cards
无法容纳所有卡片 .card
时,它会自动换行,并且最后一个卡片宽度变宽了,这是因为卡片 .card
设置了flex-grow:1
。
看下图卡片align-items: stretch即可
均分列(等分列)布局
在 Web 中均分列的布局效果很多,尤其是在移动端的开发当中,底部的菜单栏中的列大多都是均分的。
均分列又称为等分列或等列,它的最大特征就是列的宽度是相等的 !
以往 CSS 实现等分列都是通过百分比来计算,比如:
- 列数(
--column
); - 列间距(
--gap
)。
一般情况下,列宽就是:
列宽 = (容器宽度 - (列数 - 1)× 列间距)÷ 列数
假设是一个三列,列间距为0
,那么每列的宽度将会是:
.column {
/**
* 容器宽度 ➜ 100%
* 列数 ➜ --columns = 3
* 列间距 ➜ --gap = 0
* 列宽 = ((100% - (3 - 1) × 0) ÷ 3 = 33.3333%
**/
width: 33.33333%;
}
或者使用 calc()
函数和 CSS 的自定义属性结合:
:root {
--container-width: 100%; /* 容器宽度 */
--columns: 3; /* 列数 */
--gap: 0 ; /* 列间距 */
}
.column {
width: calc((var(--container-width) - (var(--columns) - 1) * var(--gap)) / var(--columns));
}
但不管是哪种方式,开发者都需要提前知道等分的列数、列间距等,对于构建一个动态的等分列,上面方案的缺陷就出来了。开发者需要不断地去做数学计算,而且是需要知道参数的情况之下才行 。
如果换成 Flexbox 技术来构建的话,开发者就不需要去考虑这些参数了, 如图
构建它的 HTML 结构并不复杂:
<footer>
<div class="item">
<Icon /> Icon name
</div>
<!-- 省略其他的菜单项 -->
</footer>
footer {
display: flex;
}
.item {
flex: 1;
min-width: 0; /* 这行代码很重要 */
}
/* 菜单项图标和文字的布局 */
.item {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
}
使用 flex:1
来均分列(即平均分配 Flex 容器可用空间)并不是完全可以的,它需要配合 min-width:0
一起使用。因为在 Flex 项目上显式设置 flex:1
时已重置了 flex
的初始值(flex: 0 1 auto
),浏览器会把 flex:1
计算成:
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%
当 flex-basis
为 auto
时, Flex 项目的宽度是 max-content
(除非 Flex 容器空间完全不足)。也就是说,flex:1
时,flex-basis
值从 auto
变成了0%
,这将覆盖 Flex 项目的内在尺寸(min-content
),Flex 项目的基本尺寸现在是 0
,但由于 flex-grow
的存在,Flex 项目会扩展以填补空的空间(Flex 容器的剩余空间)。
而实际上,在这种情况下,flex-shrink
不再做任何事情,因为所有 Flex 项目现在的宽度都是 0
,并且正在增长以填补可用空间。只不过, Flex 容器有可能存在没有剩余空间的情况,甚至是有不足空间的情况存在。此时,flex:1
也就不能均分 Flex 容器的可用空间。
圣杯布局
圣杯布局(Holy Grail Layout)是 Web 中典型的布局模式
就上图而言,这就是一个非常普通的三列布局。对圣杯布局有一定了解的同学都应该知道,构建圣杯布局时,对 HTML 的结构是有一定的要求,即 主内容为先 。早期这样做,是让用户在 Web 页面加载缓慢时,就能先看到主内容。
<!-- HTML -->
<header>
<!-- 页头 -->
</header>
<main>
<!-- 页面主体,它包含两个侧边栏和一个主内容列 -->
<article>
<!-- 页面主内容列,需要排在 nav 和 aside 前面 -->
</article>
<aside>
<!-- 侧边导航栏 -->
</aside>
<aside>
<!-- 侧边内容栏,比如广告栏 -->
</aside>
</main>
<footer>
<!-- 页脚 -->
</footer>
对于经典的圣杯布局,它有:
- 页头
<header>
; - 页脚
<footer>
; - 主内容
<article>
; - 左侧边栏
<aside>
; - 右侧边栏
<aside>
。
它应该具备的能力:
- 在 HTML 文档的源码中,主内容
<article>
要位于两个侧边栏<aside>
之前; - 页头
<header>
和页脚<footer>
并没有固定高度,即它们的height
为auto
,由其内容或相关盒模型属性值(比如padding
、margin
或border
)决定大小; - 在垂直方向,中间三列(
<main>
)的高度占据<header>
和<footer>
之外的浏览器视窗高度,并且随着内容增多而变高; - 在水平方向,一般情况之下两个侧边栏也是由其内容来决定大小,但多数情况之下会给两个侧边栏设置一个固定宽度,比如左侧边栏是
220px
,右侧边栏是320px
。中间主内容列<article>
占据两侧边栏之外的浏览器视窗宽度,并且随着内容增加,不会出现水平滚动条。
我们来看一个真实的圣杯布局案例: 可以结合 CSS 的媒体查询,在小屏幕下调整布局,构建一个具有响应式能力的圣杯布局:
body {
display: flex;
flex-direction: column;
gap: 1px;
min-height: 100vh;
}
main {
flex: 1 1 0%;
min-height: 0;
display: flex;
gap: 1px;
}
aside {
min-width: 220px;
max-width: 320px;
}
aside:nth-of-type(1) {
order: -1;
}
article {
flex: 1 1 0%;
min-width: 0;
}
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
footer {
display: flex;
justify-content: center;
}
@media screen and (max-width: 800px) {
main {
flex-direction: column;
}
main aside {
width: 100%;
max-width: none !important;
}
aside:nth-of-type(1) {
order: 1;
}
}
页脚(Footer)的位置会随着页头(Header)和主内容(Content)高度而变化,但当页头和主内容内容较小,其高度总和小于浏览器视窗高度时,页脚要始终位于浏览器视窗底部。
对于 Sticky Footer 的布局,使用 Flexbox 再容易不过了,只需要保持主内容容器(它也是一个 Flex 项目)能随着它的父容器(Flex 容器)的剩余空间扩展。简单地说,给主内容设置一个 flex-grow
值为 1
即可。具体代码如下:
<body>
<header>
</header>
<main>
</main>
<footer>
</footer>
</body>
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex-grow: 1;
}
如果你不希望浏览器视窗高度较小时,主内容的高度进行收缩,你可以将其 flex-shrink
设置为 0
,比如:
main {
flex: 1 0 auto;
/* 等同于 */
flex-grow: 1;
flex-shrink: 0;
flex-basis: auto;
}
同样地,为了避免页头和页脚因浏览器视窗高度较小时被挤压,建议在它们上面设置 flex-shrink
值为 0
。
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
header, footer {
flex-shrink: 0;
}
main {
flex: 1 0 auto;
min-height: 0;
}
12 列网格布局
12 列网格布局最早源于 960gs 网格布局系统,它和 CSS 原生的网格系统不是同一个东西。简单地说,960gs 就是将页面分成12列,有列宽和列间距,然后页面的布局划分到具体的列上面,如下图所示:
早期的 960gs 都是使用 CSS 的浮动(float
)来构建的,不过现在很多 CSS 框(CSS Frameworks)中的网格系统都采用 Flexbox 来构建,比如 Bootstrap的网格系统 现在就是采用 Flexbox 布局构建的。
相对而言,使用 Flexbox 技术构建 960gs 网格系统要比浮动技术简单得多,你只需要在一个 Flex 容器放置所需要的列数,每一列对应着一个 Flex 项目。比如:
HTML
<!-- 12列:flex 容器中包含 12 个 flex 项目 -->
<flex-container>
<flex-item> 1 of 12</flex-item>
<!-- 中间省略 10个 flex-item -->
<flex-item> 1 of 12</flex-item>
</flex-container>
<!-- 6列: flex 容器中包含 6个 flex 项目 -->
<flex-container>
<flex-item> 1 of 6 </flex-item>
<!-- 中间省略 4 个 flex-item -->
<flex-item>1 of 6 </flex-item>
</flex-container>
<!-- 4列: flex 容器中包含 4 个 flex 项目 -->
<flex-container>
<flex-item> 1 of 4 </flex-item>
<!-- 中间省略 2 个 flex-item -->
<flex-item>1 of 4 </flex-item>
</flex-container>
<!-- 3列:flex 容器中包含 3 个 flex 项目 -->
<flex-container>
<flex-item> 1 of 3</flex-item>
<flex-item> 1 of 3</flex-item>
<flex-item> 1 of 3</flex-item>
</flex-container>
<!-- 2列: flex 容器中包含 2个 flex 项目 -->
<flex-container>
<flex-item> 1 of 2</flex-item>
<flex-item> 1 of 2</flex-item>
</flex-container>
对于 CSS 而言,主要使用 Flexbox 的 gap
属性来设置列与列之间的间距,然后在 Flex 项目上使用 flex
属性即可,比如:
flex-container {
display: flex;
gap: var(--gap, 1rem)
}
flex-item {
flex: 1 1 0%;
min-width: 0; /* 建议加上 */
}
九宫格布局
九宫格简单地说就是一个 3 × 3
的网格(三行三列),它也常用于 Web 布局中,而且你可以基于它演变出很多种不同的布局风格:
实现上图的布局效果,所需要的 HTML 结构可能会像下面这样:
<div class="card">
<div class="card__heading">
<h3 class="card__title">现代 Web 布局</h3>
<p class="card__describe">使用 Flexbox 技术构建九宫格布局</p>
</div>
<div class="card__grid">
<div class="card__row">
<div class="card__column">
<figure>
<img src="cat.png" alt="" />
</figure>
</div>
<div class="card__column">
<figure>
<img src="cat.png" alt="" />
</figure>
<figure>
<img src="cat.png" alt="" />
</figure>
<figure>
<img src="cat.png" alt="" />
</figure>
<figure>
<img src="cat.png" alt="" />
</figure>
</div>
</div>
</div>
</div>
关键的 CSS 代码:
.card {
display: flex;
flex-direction: column;
gap: 1rem;
}
.card__grid {
display: flex;
flex-direction: column;
}
.card__row {
display: flex;
gap: 1rem;
}
.card__column {
flex: 1 1 calc((100% - 1rem) / 2);
min-width: 0;
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card__column:nth-child(2) figure {
flex: 1 1 calc((100% - 1rem) / 2);
min-width: 0;
}