一、实现垂直居中的方法
1.1 五种常用的垂直居中方法
在前端开发过程中,一个子元素在其父元素中垂直居中的场景很常见,也是很多初级程序员面试中的必考题目。总的来说垂直居中的方法有以下几种:
- line-height 【可以用】
- vertical-align 【不要用】
- flex 布局 【建议用】
- 绝对定位 + transfrom 【看情况】
- grid 布局 【能不用就不用】
理论上是这样的,但是在实际开发过程中会遇到很多问题,比如 vertical-align 并不是在所有的场景都生效;再比如在父元素比较小、内容比较多的时候使用 flex + align-items + justify-content 的布局会将内容裁剪。
所以我这篇文章将总结一下项目实践中,对于垂直居中的需求,应该选择哪种布局方式才更科学。
1.2 垂直居中应用场景分类
我在本篇文章中将垂直居中的应用场景分为两大类
1.2.1 小模块
第一类是【小模块的垂直居中】,一般是指按钮、卡片等内容比较少,且不会出现滚动条的情况。
1.2.2 大模块
第二类是【大模块的垂直居中】,比如全局弹框、大范围的页面布局、整个页面的居中布局等,一般是内容很多,父元素可能会有滚动条的情况。
这两类应用场景的实现在开发中对于垂直居中方法的选择还是有点区别的。
二、小模块的垂直居中
前面说到“小模块”一般指按钮、卡片等不会出现滚动条的情况,这种我们一般采用的方法有:
- line-height:常用于按钮等单行文本
- vertical-align:仅适用于表格单元格(table/table-cell)中
- flex 布局,很常用,很方便的居中
2.1 使用 line-height 行高
可以使【 line-height = 父容器高度】来实现内容垂直居中的效果,特别适用于父容器的高度确定的场景,比如按钮。
<template>
<div class="content">今天是个好日子</div>
</template>
<style lang="scss" scoped>
.content {
border: 1px solid;
width: 200px;
// height: 100px; // 可以不指定高度,那么高度就是 line-height 的值
line-height: 120px; //line-height 行高会撑起父容器的高度
text-align: center; // 水平居中
font-size: 20px;
}
</style>
效果如下

2.2 关于 vertical-align: middle
说实话自从有了 flex 布局我已经很少用到 vertical-align 这个属性了,但是当我整理这篇文章的时候,我发现这个属性好像没有那么简单,它的生效时有条件的,可以先看一下 mdn 的官方文档。
文档中指出 vertical-align 属性可以被用于两中上下文
- 使行内元素盒模型与其行内元素容器垂直对齐
- 垂直对齐表格单元格的内容
也就是说 vertical-align 值对行内元素(display:inline)、行内块元素(display:inline-block)、和表格单元格生效,不能用它垂直对齐块级元素。
理论时这样的,但是实际应用中发现好像没有那么简单。
2.2.1 不生效的 vertical-align
下面的代码不能使子元素(content)在父容器(outer)中垂直居中。
<template>
<div class="outer">
<span class="content">你好</span>
</div>
</template>
<style lang="scss" scoped>
.outer {
border: 1px solid;
height: 200px;
width: 300px;
display: inline-block; // 使父容器变成行内块元素
vertical-align: middle; // 使其垂直居中,这样也不对
.content {
display: inline-block;
font-size: 20px;
vertical-align: middle; // 使其垂直居中,这样写也不对
border: 1px solid red;
}
}
</style>
代码的实际效果如下:

这跟我们想要的不一样,因为我们没有弄清楚 vertical-align 的作用,这里再次强调一下:
给元素 A 设置 vertical-align:middle 不能让元素 A 在其父容器 B 中垂直居中,只能让 A 和它同行的行内兄弟元素对齐。
换句话说:vertical-align:middle 是在行内格式化上下文(IFC)中,让同行的行内元素在垂直方向上彼此对齐的手段,不能让元素在父容器中垂直居中。
这里提到的【行内格式化上下文 IFC】,行内格式化上下文的创建是自动触发的,不需要像BFC(块级格式化上下文)那样通过特定的 css 属性显示创建。当容器内容包含行内元素/行内块元素,浏览器就会自动生成 IFC,也就是说有一个【行内元素/行内块元素】那么它所在的父容器的环境,就是一个IFC。
回到 vertical-align 属性,它只对【行内元素/行内块元素】生效,且只能让同一个【行内格式化上下文】中的【行内元素/行内块元素】对齐。
2.2.2 同行元素彼此对齐(中线对中线)
看一下,下面的例子,只能让两个字体不一样的元素在一个行上对齐,而不是在父元素中对齐。
<template>
<div class="outer">
<span class="content">你好</span>
<span class="text">,明天</span>
</div>
</template>
<style lang="scss" scoped>
.outer {
border: 1px solid;
height: 200px;
width: 300px;
display: inline-block; // 这个其实是没用的
.content {
display: inline-block; // 给每个元素设置成行内块,而不是父元素
font-size: 20px;
vertical-align: middle; // 给每个元素设置中线对齐,而不是父元素
border: 1px solid red;
}
.text {
display: inline-block; // 给每个元素设置成行内块,而不是父元素
font-size: 14px;
vertical-align: middle; // 给每个元素设置中线对齐,而不是父元素
border: 1px solid blue;
}
}
</style>
效果如下:

这种情况在实际开发中也有用到,比如在一个 div 标签中有文字、有图片,让图片和文字对齐就可以用给图片设置 vertical-align: middle。
<template>
<div class="outer">
你好
<img src="@/assets/images/vite.svg" />
</div>
</template>
<style lang="scss" scoped>
.outer {
border: 1px solid;
height: 100px;
width: 200px;
font-size: 20px;
line-height: 1;
img {
display: inline-block;
width: 10px;
// vertical-align: top; //可以自己体会一下不同值的对齐方式
vertical-align: middle;
}
}
</style>

关于这点,可以看一下MDN中的例子
2.2.2 vertical-align 生效的场景
上面说到 vertical-align 不能使元素在父元素中垂直居中,但是其实有一种场景可以实现,那就是在表格的单元格内,使用方法有两种,一种是使用原生的 table + tr/td 等标签,第二种是给父元素和子元素转成表格单元格布局。
(1)原生 table 标签中的垂直居中
<template>
<table>
<tr>
<th class="test1">你好</th>
<th class="test2">明天</th>
</tr>
<tr>
<th>你好</th>
<th>明天</th>
</tr>
</table>
</template>
<style lang="scss" scoped>
table {
border: 1px solid red;
width: 500px;
tr {
height: 50px;
th {
border: 1px solid;
height: 50px;
width: 50px;
font-size: 12px;
}
.test1 {
vertical-align: sub; // 换一个居中凡事方便对比
}
.test2 {
vertical-align: middle; // 内容垂直居中
}
}
}
</style>
效果如下图,因为 chrome 浏览器默认是 vertical-align:middle,所以没有设置特殊样式的也居中 了,但是我们可以根据 test1 的 vertical-align:sub 对比出来差距,从而得知 vertical-align 这个属性在单元格内是生效了的。

(2)自定义单元格布局
<template>
<div class="content">
<div class="text">你好</div>
<div class="text text2">明天</div>
</div>
</template>
<style lang="scss" scoped>
.content {
border: 1px solid red;
width: 500px;
height: 200px;
display: table;
.text {
border: 1px solid;
display: table-cell; // 最好不要单独使用,可能会出现问题
font-size: 16px;
}
.text2 {
vertical-align: middle; // 内容垂直居中
}
}
</style>
效果如下图:

使用display: table + display: table-cell 可以模拟表格的布局,在 table-cell 中使用 vertical-align: middle; 可以实现内容垂直居中。
但是实际上表格布局也不常用,虽然它的兼容性很好,之前在需要兼容 ie 的时候可以用一用,现在多数都是用 flex 布局了。
所以总的来说,在实现内容垂直居中的需求中我们一般不用 vertical-align: middle 这个属性。
2.3 flex 布局
flex 布局真是个好东西,实现垂直居中的三件套如下:
<template>
<div class="content">你好</div>
</template>
<style lang="scss" scoped>
.content {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid red;
width: 100px;
height: 100px;
}
</style>

在项目开发中,我的建议是能用 flex布局就用,当你写了一个新的 div, 养成习惯第一件事就是给它加上 display: flex 或者是 inline-flex,可以避免很多麻烦。
2.4 小结
使用 flex 布局实现水平/垂直居中是最推荐的一种方式。line-height 也可以用,但是 vertical-align 不到万不得已就不要用了。
三、大模块的垂直居中
这里“大模块”一般指的是,全局弹框、大范围的页面布局,一般来说使用以下三种方式:
- flex 布局 【建议用】
- 绝对定位 + transfrom 【看情况用】
- grid 【能不用就不用】
3.1 flex布局实现垂直居中
flex 布局在小模块功能中实现垂直居中可以说是完美的,但是如果在大模块的布局中是会有问题的。在父容器高度较小、内容较多时,给父容器设置 overflow:auto,内容区域会被裁剪,展示不全,看下面的例子。
3.1.1 代码
<template>
<div class="outer-wrapper">
<div class="content">方法一</div>
</div>
</template>
<style lang="scss" scoped>
.outer-wrapper {
width: 100vw; // 宽高固定
height: 100vh;
display: flex; // 使用flex布局实现垂直居中
align-items: center;
justify-content: center;
border: 2px solid;
padding: 20px;
overflow: auto; // 内容过多时滚动
.content {
width: 100%;
height: 800px;
flex-shrink: 0;
background: #ccc;
}
}
</style>
3.1.2 父容器足够大时
上述代码,在父容器足够大,也就是内容较少时,效果如下图,可以正常实现内容的垂直居中,没有任何问题。

3.1.3 父容器较小时
在父容器较小时,也就是内容较多时,效果如下图,即便给父容器设置了 overflow: auto; 内容模块在边界时无法完整的展示,内容被裁剪了。

3.1.4 safe 关键字
所以这样看来 flex 布局也不是完美的,不过 flex 布局有一个关键字 safe 可以使用 ,在绝大多数浏览器上可以解决上述问题,但是在低版本的 safari 浏览器不兼容,具体可以看一下这篇文章,这篇文章,提供了一个解决方案,但是也不算完美,完美的解决方案我会在后面给出。
3.2 绝对定位实现垂直居中
使用绝对定位 fixed + top: 50% + translateY(-50%) 实现的垂直居中,和 flex 布局存在同样的问题,在父容器高度较小、内容较多时,给父容器设置 overflow:auto,内容区域会被裁剪,展示不全,看下面的例子。
3.2.1 代码
<template>
<div class="outer-wrapper">
<div class="content">方法二</div>
</div>
</template>
<style lang="scss" scoped>
.outer-wrapper {
position: relative;
width: 100vw; // 宽高固定
height: 100vh;
border: 2px solid;
padding: 20px;
overflow: auto; // 内容过多时滚动
.content {
position: absolute; // 绝对定位
top: 50%; // 使用 top + translate 使内容居中
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 300px;
background: #ccc;
}
}
</style>
3.2.2 父容器足够大时
上述代码,在父容器足够大,也就是内容较少时,效果如下图,可以正常实现内容的垂直居中,没有任何问题。

3.2.3 父容器较小时
在父容器较小时,也就是内容较多时,效果如下图,即便给父容器设置了 overflow: auto; 内容模块在边界时无法完整的展示,内容被裁剪了。

3.2.4 绝对定位的精度问题
在使用绝对定位 top/left 实现垂直居中的时候,除了 3.2.3 提到的父容器较小的时候内容会被裁剪,还有一个问题,那就是 top 定位的精度在某些浏览器中会精确到整数,这就导致在一些低分辨率的显示器上,使用 top/left 定位元素中的文字内容会模糊。
比如:
top: 12.3px→ 实际渲染为12px
但是相对的 transform 的精度就是小数级别的(即:支持亚像素级渲染)。
比如:transform: translate(45.6px, 12.3px) → translate(45.6px, 12.3px) 【不会省略】
这就是我上面说的方法二实现全局垂直居中的弹框时会出现的第二个问题【在分辨率很低的显示器上内容的字体会模糊】
关于 top 这种布局元素的精度问题其实在其他需求中也会遇到,比如我之前遇到过一个需求,实现一个可以拖拽的按钮,使用 top 定位,每次鼠标拖动会根据鼠标位置更新按钮的 top值。
但是实现的效果按钮会抖动一下,这是因为我们获取的鼠标位置是包含小数点的(如: 100.12px),但是设置完 top 值( top: 100.12px),浏览器渲染是会都舍去小数点渲染成top: 100px。
这就导致按钮频繁的都懂,所以后面改成 transform 就好了。
3.2.5 使用绝对定位的场景
绝对定位的垂直居中常用于悬浮按钮、或者不能改变父元素原本的页面结构的需求中,但是也要注意不能有滚动条,否则也会有内容裁剪的问题。
3.2 解决办法
那么我们应该如何解决上述问题呢,目前我发现有两种解决办法。
3.2.1 三层 DOM 结构
虽然这种方法可以实现功能,但是需要在父元素和 content 之间新包一层 div【inner-wrapper】,且有很多限制,比如内层 inner-wrapper 不能设置高度,且增加了一层冗余的div,很麻烦。
<template>
<div class="outer-wrapper">
<div class="inner-wrapper">
<div class="content">真正的内容区域</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.outer-wrapper {
width: 100%; // 外层设置宽高
height: 100%;
border: 2px solid;
display: flex;
flex-direction: column;
align-items: center; // 外层可以设置垂直和水平居中
justify-content: center;
padding: 20px;
.inner-wrapper {
border: 2px solid red;
width: 80%; // 内层只能设置宽度,不能设置高度,高度让内容自动撑开,才能达到居中的效果
display: flex;
flex-direction: column;
align-items: center; // 内层只能设置水平居中,不能设置垂直居中
padding: 20px;
overflow: auto; // 给内层设置overflow
.content {
width: 100%;
height: 2000px;
flex-shrink: 0;
background: #ccc;
}
}
}
</style>
3.2.2 完美的解决方案
下面有一种完美的实现方式: flex + margin:auto。
(1) flex-direction: row 模式
<template>
<div class="outer-wrapper">
<div class="content">真正的内容区域</div>
</div>
</template>
<style lang="scss" scoped>
.outer-wrapper {
width: 100%; // 外层设置宽高
height: 100%;
border: 2px solid;
display: flex;
justify-content: center; // 可以设置水平居中,但实际不会用这个属性布局
align-items: center; // 可以设置垂直居中,但实际不会用这个属性布局
padding: 20px;
overflow: auto; // 设置滚动
.content {
margin: auto; // 实际的垂直居中布局使用的属性
width: 100%;
height: 2000px;
flex-shrink: 0;
background: #ccc;
}
}
</style>
上面的代码,父元素只需要按照普通的 flex 布局写就行,内容元素增加 margin: auto,就可以实现内容在父元素中的垂直居中。
这里面的知识点是,flex 布局中的每个项目设置了 margin:auto 之后会利用剩余空间自动分配外边距,且优先级高于父元素的 justify-content 和 align-items。
除了 flex 布局,grid 布局也可以使用 margin: auto 实现垂直水平居中,但是 gird 布局兼容性不如flex,虽然它也很强大,但是在垂直居中这种需求上用的也不多,这里就不多说了。
(2) flex-direction: column 模式
注意事项,上面的例子中 flex-direction: row,也就是默认的,在 row 模式下是没问题的,但是如果手动设置了 flex-direction: column; 就需要改一下代码了
<template>
<div class="outer-wrapper">
<div class="content">真正的内容区域</div>
</div>
</template>
<style lang="scss" scoped>
.outer-wrapper {
width: 100%; // 外层设置宽高
height: 100%;
border: 2px solid;
display: flex;
flex-direction: column; // 设置flex布局主轴为 column
// justify-content: center; // 一定不能设置这个属性!!
align-items: center; // 可以设置垂直居中,但实际不会用这个属性布局
padding: 20px;
overflow: auto; // 设置滚动
.content {
margin: auto; // 实际的垂直居中布局使用的属性
width: 100%;
height: 2000px;
flex-shrink: 0;
background: #ccc;
}
}
</style>
3.2.3 elementPlus 实现方法
我看了一下 elementPlus 全局居中弹框的实现原理,发现就是使用了 flex + marign:auto。

四、总结
对于不同的页面上垂直居中的场景,选对方法很重要,一般来说用 flex 布局就对了。对于父元素滚动的场景,请把使用 flex + margin: auto 这种垂直居中的实现方法牢记于心,可以大大的提高你的工作效率。
从此之后,所有关于显示不全的 bug 都可以在5分钟之内解决了,不能的话,再从头看一下这篇文章,哈哈哈。
内容较长,难免疏漏,有问题欢迎指出。
欢迎关注我的专栏,持续更新高质量博客。
CSDN
https://mp.csdn.net/mp_blog/manage/column/columnManage/12388571
1210

被折叠的 条评论
为什么被折叠?



