如何实现水平垂直居中 可以说是CSS面试题中的经典面试题,在多年前这个面试题给很多同学都带来了困惑,但 Flexbxo布局模块 和 CSS Grid布局模块 的到来,可以说实现水平垂直居中已是非常的容易。
▐ Flexbox中实现水平垂直居中
在Flexbox布局模块中,不管是单行还是多行,要让它们在容器中水平垂直居中都是件易事,而且方法也有多种。最常见的是在Flex容器上设置对齐方式,在Flex项目上设置margin:auto
。
先来看在Flex容器上设置对齐方式。
Flex容器和Flex项目上设置对齐方式
你可能已经知道在Flex容器上设置justify-content
、align-items
的值为center
时,可以让元素在Flex容器中达到水平垂直居中的效果。来看一个示例:
/* CSS */
.flex__container {
display: flex;
justify-content: center;
align-items: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/YzwYRRy)
这种方式特别适应于让Icon图标在容器中水平垂直居中,不同的是在Icon图标容器上显示设置display: inline-flex。比如下面这个示例:
/* CSS */
.flex__container {
display: inline-flex;
align-items: center;
justify-content: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/xxZpQNv)
在这种模式之下,如果要让多个元素实现水平垂直居中的效果,那还需要加上flex-direction: column,比如:
/* CSS */
.flex__container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/QWyazpZ)
在Flexbox布局中,还可以像下面这样让Flex项目在Flex容器中达到水平垂直居中的效果:
/* CSS */
.flex__container {
display: flex; // 或inline-flex
justify-content: center;
}
.flex__item {
align-self: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/yLepGKW)
如果在Flex容器中有多个Flex项目时,该方法同样有效:
.flex__container {
display: flex; // 或inline-flex
justify-content: center;
}
.flex__container > * {
align-self: center;
}
比如下面这个效果:
Demo(https://codepen.io/airen/embed/bGEaOjm)
除此之外,还可以使用place-content: center让Flex项目实现水平垂直居中:
.flex__container {
display: flex;
place-content: center;
}
.flex__item {
align-self: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/gOPoZQz)
或者换:
.flex__container {
display: flex;
place-content: center;
place-items: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/JjGMwzE)
这两种方式同样适用于Flex容器中有多个Flex项目的情景:
.flex__container {
display: flex;
flex-direction: column;
place-content: center;
}
.flex__container > * {
align-self: center;
}
// 或
.flex__container {
display: flex;
flex-direction: column;
place-content: center;
place-items: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/XWXVoLd)
可能很多同学对于place-content
和place-items
会感到陌生。其实place-content
是align-content
和justify-content
的简写属性;而place-items
是align-items
和justify-items
的简写属性。即:
.flex__container {
place-content: center;
place-items: center;
}
等效于:
.flex__container {
align-content: center;
justify-content: center;
align-items: center;
justify-items: center;
}
虽然扩展出来有四个属性,但最终等效于:
.flex__container {
display: flex;
align-items: center;
justify-content: center;
}
// 多行
.flex__container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
在Flex项目上设置`margin: auto`
如果在Flex容器中只有一个Flex项目,还可以显式在Flex项目中显式设置margin
的值为auto
,这样也可以让Flex项目在Flex容器中水平垂直居中。例如:
.flex__container {
display: flex; // 或 inline-flex
}
.flex__item {
margin: auto;
}
效果如下:
Demo(https://codepen.io/airen/embed/KKVZJNp)
整个过程,你可以通过下面这个示例来体验。尝试着选中不同方向的margin
值:
Demo(https://codepen.io/airen/embed/gOPoqRq
▐ Grid中实现水平垂直居中
CSS Grid布局可以说是现代Web布局中的银弹。它也是到目前为止布局系统中唯一一个二维布局系统。
在CSS Grid布局中,只需要仅仅的几行代码也可以快速的帮助我们实现水平垂直居中的效果。比如下面这个示例:
/* CSS */
.grid {
display: grid; // 或 inline-grid
place-items: center
}
效果如下:
Demo(https://codepen.io/airen/embed/zYrRYxW)
在CSS Grid布局模块中,只要显式设置了display: grid
(或inline-grid
)就会创建Grid容器和Grid项目,也会自动生成网格线,即行和列(默认为一行一列)。
在没有显式地在Grid容器上设置grid-template-columns
和grid-template-rows
,浏览器会将Grid容器默认设置为Grid内容大小:
这种方法也适用于CSS Grid容器中有多个子元素(Grid项目),比如:
这个时候你看到的效果如下:
Demo(https://codepen.io/airen/embed/PoZQoGP)
而且palce-items
适用于每个单元格。这意味着它将居中单元格的内容。比如下面这个示例:
Special title treatment
With supporting text below as a natural lead-in to additional content.
/* CSS */
.grid__container {
display: grid;
place-items: center;
grid-template-columns: repeat(2, 1fr);
gap: 2vh;
}
.grid__item {
display: grid;
place-items: center;
}
效果如下:
Demo(https://codepen.io/airen/embed/mdVXdpe)
等高布局
等高布局也是Web中非常常见的一种布局方式,而且实现等高布局的方案也有很多种。这里我们主要来看Flexbox布局模块和Grid布局模块给我们带来了什么样的变化。
在Flexbox和Grid布局模块中,让我们实现等高布局已经是非常的简单了,比如:
/* CSS */
.flex__container {
display: flex; // 或 inline-flex
}
简单地说,在容器上显式设置了display
的值为flex
或inline-flex
,该容器的所有子元素的高度都相等,因为容器的align-items
的默认值为stretch
。
这个时候你看到的效果如下:
Demo(https://codepen.io/airen/embed/NWxyOQq)
这种方式特别适用于卡片组件中:
Demo(https://codepen.io/airen/embed/OJMQoOO)
在Grid布局模块中类似:
/* CSS */
.grid__container {
display: grid;
grid-template-columns: 20vw 1fr 20vw; /* 根据需求调整值*/
}
效果如下:
Demo(https://codepen.io/airen/embed/mdVXQON)
同样在一些卡片类布局中运用:
Demo(https://codepen.io/airen/embed/MWKQzmE)
如果需求有所调整,比如在Flex项目 或 Grid项目的子元素高度和容器高度相同。
/* CSS */
.flex__container {
display: flex;
}
.content {
height: 100%
}
// 或
.grid__container {
display: grid;
grid-auto-flow: column;
}
.content {
height: 100%;
}
效果如下:
Demo(https://codepen.io/airen/embed/jOWZdbo)
基于Sticky Footer
首先用下图来描述什么是Sticky Footer布局效果:
Sticky Footer实现方案和等高、垂直居中一样,同样有很多种方案可以实现(//css-tricks.com/couple-takes-sticky-footer/)。
比如像下面这样的结构:
先来看Flexbox布局模块中的实现方案:
body {
display: flex;
flex-direction: column;
}
footer {
margin-top: auto;
}
Demo(https://codepen.io/airen/embed/bGELzYy)
可以尝试着在main
区域右下角向下拖动,改变主内容区域的高度,你会发现“当内容不足一屏时,
会在页面的最底部,当内容超出一屏时,
会自动往后延后”。
在Flexbox布局中,还可以在
区域上设置下面的样式,达到相等的效果:
body {
display: flex;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
效果如下:
Demo(https://codepen.io/airen/embed/zYrRXmY)
中的flex: 1 0 auto
相当于是:
main {
flex-grow: 1; /*容器有剩余空间时,main区域会扩展*/
flex-shrink: 0; /*容器有不足空间时,main区域不会收缩*/
flex-basis: auto; /*main区域高度的基准值为main内容自动高度*/
}
如果你想省事的话,可以在main
上显式设置flex-grow:1
,因为flex-shrink
和flex-basis
的默认值为1
和auto
。
在CSS Grid布局中我们可以借助1fr
让
区域根据Grid容器剩余空间来做计算。
.grid__container {
display: grid;
grid-template-rows: auto 1fr auto;
}
效果如下:
Demo(https://codepen.io/airen/embed/MWKQRxd)
均分列
在Web布局中,很多时候会对列做均分布局,最为常见的就是在移动端的底部Bar,比如下图这样的一个效果:
在Flexbox和Grid还没出现之前,如果希望真正的做到均分效果,可以用100%
(或100vw
)除以具体的列数。比如:
container>
column>column>
column>column>
column>column>
container>
/* CCSS */
.container {
inline-size: 50vw;
min-inline-size: 320px;
display: flex-row;
}
.column {
float: left;
width: calc(100% / 3);
}
效果如下:
Demo(https://codepen.io/airen/embed/LYGQoxL)
通过浏览器调试器中可以发现,现个列的宽度都是相等的:
在Flexbox和Grid布局中,实现上面的效果会变得更容易地多。先来看Flexbox中的布局:
flex__container>
flex__item>flex__item>
flex__item>flex__item>
flex__item>flex__item>
flex__container>
/* CSS */
.flex__container {
inline-size: 50vw;
display: flex;
}
.flex__item {
flex: 1;
}
效果如下:
Demo(https://codepen.io/airen/embed/yLevWEe)
在Flexbox布局模块中,当flex
取的值是一个单值(无单位的数),比如示例中的flex:1
,它会当作显式的设置了flex-grow: 1
。浏览器计算出来的flex
:
接下来看Grid中如何实现上例的效果:
grid__container>
grid__item>grid__item>
grid__item>grid__item>
grid__item>grid__item>
grid__container>
/* CSS */
.grid__container {
display: grid;
grid-template-columns: repeat(3, 1fr); /*这里的3表示具体的列数*/
}
最终的效果是相同的:
Demo(https://codepen.io/airen/embed/NWxyVQP)
这样的布局方式也适用于其他的布局中。但不管是Flexbox还是Grid布局中,都存在一定的缺陷,当容器没有足够的空间容纳Flex项目(或Grid项目)时,Flex项目或Grid项目会溢出(或隐藏,如果Flex容器或Grid容器显式设置了overflow:hidden
):
修复这种现象最简单的方式是在Flex容器或Grid容器显式设置一个min-width
(或min-inline-size
):
.flex__container {
min-inline-size: 300px;
}
不过话又说回来,比如我们的Flex项目(或Grid项目)是一个卡片,每张卡片宽度是相等之外,更希望容器没有足够空间时,Flex项目(或Grid项目)会自动断行排列。
我们继续通过示例向大家展示。先来看Flexbox实现方案:
.flex__container {
display: flex;
flex-wrap: wrap;
}
.flex__item {
flex: 0 1 calc((100vw - 18vh) / 4); /* calc(100vw -18vh) / 4 是flex-basis的基准值 */
}
Demo(https://codepen.io/airen/embed/dyGdBpw)
你可以尝试着调整浏览器的视窗宽度,当浏览器的视窗越来越小时,Flex容器宽度也就会越来越小,当Flex容器小到没有足够的空间容纳四个Flex项目(就此例而言),那么Flex项目就会断行排列:
基于该例,如果把Flex项目的flex
值改成:
.flex__item {
flex: 0 0 400px;
}
这个时候,当Flex容器没有足够空间时,Flex项目会按flex-basis: 400px
计算其宽度,Flex容器没有足够空间时,Flex就会断行:
反过来,如果Flex项目的值flex
改成:
.flex__item {
flex: 1 0 400px;
}
当Flex容器没有足够空间排列Flex项目时,Flex项目会按flex-basis: 400px
计算其宽度,Flex会断行,并且同一行出现剩余空间时,Flex项目会扩展,占满整个Flex容器:
在Grid中实现类似的效果要更复杂一点。可以使用repeat()
函数,1fr
以及auto-fit
等特性:
.grid__container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2vh;
}
效果如下:
Demo(https://codepen.io/airen/embed/RwrQzeN)
如果你对这方面知识感兴趣的话,还可以移步阅读《Container Query Solutions with CSS Grid and Flexbox(//moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/)》一文。
其实在Grid中与auto-fit
对比的值还有一个叫auto-fill
。但两者的差异是非常地大,用下图来描述auto-fit
和auto-fill
的差异:
另外这种方式也是到目前为止一种不需要借助CSS媒体查询就可以实现响应式布局效果。
圣杯布局
圣杯布局(Holy Grail Layout)(//en.wikipedia.org/wiki/Holygrail(web_design))是Web中典型的布局模式(//alistapart.com/article/holygrail/)。看上去像下图这样:
对于圣杯布局而言,HTML结构是有一定的要求,那就是内容为先:
header>header>
main>
article>article>
nav>nav>
aside>aside>
main>
footer>footer>
在这里主要还是和大家一起探讨,如何使用Flexbox和Grid布局模块来实现圣杯布局。先来看Flexbox实现方案:
body {
width: 100vw;
display: flex;
flex-direction: column;
}
main {
flex: 1;
min-height: 0;
display: flex;
align-items: stretch;
width: 100%;
}
footer {
margin-top: auto;
}
nav {
width: 220px;
order: -1;
}
article {
flex: 1;
}
aside {
width: 220px;
}
效果如下:
Demo(https://codepen.io/airen/embed/rNxJXYb)
通过在nav
、aside
和article
上显式设置order
的值,可以很好的控制这三个区域的布局顺序。比如说,希望
在
之前排列,只需要在上面的示例基础上做一点点调整:
nav {
order: 0;
}
aside {
order: -1;
}
效果如下:
注意,order
的默认值为0
,值越大越排在后面!
在上例的基础上,借助CSS媒体对象的特性,可以很容易实现响应式的圣杯布局效果:
@media screen and (max-width: 800px) {
main {
flex-direction: column;
}
nav, aside {
width: 100%;
}
}
效果如下:
Demo(https://codepen.io/airen/embed/gOPvVZX)
尝试着拖动浏览器来改变视窗大小,你可以看到如下图的效果:
在Grid布局模块中,实现圣杯布局要比Flexbox布局模块中更容易,而且更灵活。在CSS Grid布局模块中,HTML结构可以更简洁:
body>
header>header>
main>main>
nav>nav>
aside>aside>
footer>footer>
body>
在CSS方面有很多种方案可以实现圣杯布局效果。我们先来看第一种:
body {
display: grid;
grid-template: auto 1fr auto / 220px 1fr 220px;
}
header {
grid-column: 1 / 4;
}
main {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
nav {
grid-column: 1 / 2;
grid-row: 2 / 3;
}
aside {
grid-column: 3 / 4;
grid-row: 2 / 3;
}
footer {
grid-column: 1 / 4;
}
效果如下:
Demo(https://codepen.io/airen/embed/PoZRYPa)
上面示例采用的是网格线来给每个区域进行定位的:
和Flexbox布局类似,在媒体查询中可以改变每个网格区域的位置:
@media screen and (max-width: 800px) {
body {
grid-template-rows: auto;
grid-template-columns: auto;
}
header,
main,
nav,
aside,
footer {
grid-column: 1 / 2;
min-height: auto;
}
main {
grid-row: 3 / 4;
margin: 0;
}
nav {
grid-row: 2 / 3;
}
aside {
grid-row: 4 / 5;
}
footer {
grid-row: 5 / 6;
}
}
Demo(https://codepen.io/airen/embed/vYLRBaa)
除了grid-template
(即grid-template-columns
和grid-template-rows
)之外,在Grid布局中还可以使用grid-area
和grid-template-areas
属性的结合,也能很方便的实现CSS圣杯布局。基于上面的示例上,只需要把你的CSS调整为:
body {
display: grid;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
}
header {
grid-area: header;
}
main {
grid-area: main;
}
nav {
grid-area: nav;
}
aside {
grid-area: aside;
}
footer {
grid-area: footer;
}
@media screen and (max-width: 800px) {
body {
grid-template-areas:
"header"
"nav"
"main"
"aside"
"footer";
}
}
效果如下:
Demo(https://codepen.io/airen/embed/LYGdRrG)
你可能发现了它们之间的差异性:
后面这个示例中,
、
和
区域宽度相等。这是因为我们示例中通过
grid-template-areas
来声明网格,在使用grid-template-areas
创建网格时,其实也隐式的创建了网格线,只不过他和grid-template
不同的是grid-template
可以显式的指定网格轨道大小,而grid-template-areas
在该示例中相当于网格轨道大小都是1fr
。
如果我们希望
的区域变得更大,那么可以在grid-template-areas
上做个调整:
body {
display: grid;
grid-template-areas:
"header header header header header"
"nav main main main aside"
"footer footer footer footer footer";
}
效果如下:
Demo(https://codepen.io/airen/embed/QWymKYZ)
这个时候网格区域的划分像下图这样:
虽然在效果有所调整了,但还是均分状态。更好的解决方案是,将grid-template-areas
和grid-template
结合起来使用:
body {
display: grid;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
grid-template-columns: 220px 1fr 220px;
grid-template-rows: auto 1fr auto;
}
header {
grid-area: header;
}
main {
grid-area: main;
}
nav {
grid-area: nav;
}
aside {
grid-area: aside;
}
footer {
grid-area: footer;
}
@media screen and (max-width: 800px) {
body {
grid-template-areas:
"header"
"nav"
"main"
"aside"
"footer";
grid-template-columns: 1fr;
grid-template-rows: auto auto 1fr auto auto;
}
main {
margin-left: 0;
margin-right: 0;
}
}
效果如下:
Demo(https://codepen.io/airen/embed/OJMvRev)
你可以发现,这个时候,网格线的区域的命名像下图这样:
12列网格布局
12列网格布局最早是由960.gs提出的网格布局系统(//960.gs/):
12列网格布局在设计系统和CSS Framework中经常使用,比如业内经典的Bootstrap(//getbootstrap.com/)就采用了12列网格布局系统:
在社区中也有很多在线工具,帮助我们快速构建12列网格系统,比如 Free CSS Grid Tools & Resources For Developers(//1stwebdesigner.com/free-css-grid-tools-resources/) 一文中罗列的工具。
Demo (http://paulhebertdesigns.com/gridley/)
不过这里主要是想和大家一起看看在Flexbox和Grid布局模块中是如何实现12列的网格布局系统。
先来看Flexbox布局模块。12列网格布局的HTMl结构一般类似于下面这样:
flex__grid>
flex__row>
flex__item col4>flex__item col4>
flex__item col4>flex__item col4>
flex__item col4>flex__item col4>
flex__row>
flex__grid>
注意,12列网格中,一般同一行的列数值和刚好等于12
。比如上面的HTML结构,行中有三列,每列的宽度刚好四个网格宽度加两个列间距。并且在计算的时候有一套成熟的计算公式:
而且还设计上也会有所差异,比如说距离容器两侧有没有间距等:
这些的差异对于计算公式和样式代码的设计都略有差异。我们用其中一个为例:
:root {
--gutter: 10px;
--columns: 12;
--span: 1;
}
.flex__container {
display: flex;
flex-direction: column;
padding-left: var(--gutter);
padding-right: var(--gutter);
}
.flex__row {
display: flex;
margin-left: calc(var(--gutter) * -1);
margin-right: calc(var(--gutter) * -1);
}
.flex__row + .flex__row {
margin-top: 2vh;
}
.flex__item {
flex: 1 1
calc((100% / var(--columns) - var(--gutter)) * var(--span));
margin: 0 var(--gutter);
}
.flex__item1 {
--span: 1;
}
.flex__item2 {
--span: 2;
}
.flex__item3 {
--span: 3;
}
.flex__item4 {
--span: 4;
}
.flex__item5 {
--span: 5;
}
.flex__item6 {
--span: 6;
}
.flex__item7 {
--span: 7;
}
.flex__item8 {
--span: 8;
}
.flex__item9 {
--span: 9;
}