用 CSS 实现元素水平垂直居中

突然顿悟了“书读百遍,其义自现。”当然,还需要配合“好记性不如烂笔头”,整理总结一下常见的元素水平垂直居中的方法。

起因是有童鞋问我让元素垂直居中的方法,我突然懵了,居然一时没有描述出来,吓得赶紧抽空总结了一下,最后引出了元素水平且垂直居中。

方法一

最常用的方法:父元素 position: relative; 元素自身 position: absolute; top: 0; bottom: 0; margin: auto;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
position: relative;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: absolute;
top: 0;
bottom: 0;
margin: auto;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

margin: auto;很重要,只有设置了它,浏览器才会自动分配可用空间,根据规范的描述,如果“margin-top”和“margin-bottom”都是 auto,则在额外约束下,让两边得到相等的值;

其实大家都知道,这种方法通常是用来设置元素绝对居中(也就是水平垂直居中)的,规范的描述同样适用于“margin-left”和“margin-right”,所以可以达到元素绝对居中的作用,如下 👇:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
position: relative;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: absolute;
top: 0;
bottom: 0;
/* 添加以下设置 */
right: 0;
left: 0;
margin: auto;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

再来聊聊margin: auto;这个神奇的属性,通常情况下,让一个元素水平居中很简单margin: 0 auto;,而如果要让一个元素垂直居中,则需要很多的步骤,为什么?

因为浏览器的宽度是有限的,从一开始就知道父元素的宽度,然后margin: 0 auto;来分配父元素 X 轴上的可用空间,想想也很合理;

虽然浏览器的高度也是有限的,但内容的高度却是未知的,像现在的流式布局,一直滚动一直有内容,所以很难确定父元素 Y 轴到底有多长,那么margin: auto;就无法分配父元素 Y 轴上的可用空间,居中就更无从谈起了;

有人抬杠说, X 轴上的内容也可以是无限的呀,对,你说的对,反正我没见过哪家公司的站点还有横向滚动条的(特殊需求除外);

那么这里的绝对定位就好理解了:因为要分配父元素的可用空间,当然要知道父元素的边界了,所以首先父元素position: relative;,然后元素自身position: absolute;,接着定义所有边界top: 0; right: 0; bottom: 0; left: 0;,既然距离所有边界的距离都是一样的,那margin: auto;就只能给轴的两边分配相同的值,绝对居中也就成了;

方法二

也是常用方法的一种:父元素 position: relative; 元素自身 position: absolute; margin-top: -(元素自身高度的一半);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
position: relative;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: absolute;
top: 50%;
margin-top: -50px;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

绝对定位相对于最近的且 position 不是 static 的父元素,而且,定位原点位于父元素内容区域(content)的左上角,但也有例外,如果设置了left: 0; 和 top: 0;,那么,定位原点则位于父元素的实际占用区域(content + padding)的左上角,不过,这种情况并不多见;

这里的top: 50%;让元素左上角位于父元素高度一半的位置,这里是父元素高度的中心点,但元素自身是有高度的,所以还需要让元素再向上移动自身高度的一半margin-top: -50px;,来达到居中的目的;

那元素绝对居中则很简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
position: relative;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: absolute;
top: 50%;
margin-top: -50px;
/* 添加如下设置 */
left: 50%;
margin-left: -50px;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

方法三

还是最常用的方法之一:父元素 position: relative; 元素自身 position: absolute; transform: translateY(-50%);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
position: relative;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

与上一种方法大同小异,让元素左上角位于父元素高度一半的位置,然后让元素在 Y 轴上向上移动自身高度的一半;

既然能在 Y 轴上移动,当然也可以在 X 轴上移动,所以绝对居中如下 👇:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
position: relative;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: absolute;
top: 50%;
/* 添加 left 属性设置,同时设置 X Y 轴移动 */
/* transform: translateY(-50%); */
left: 50%;
transform: translate(-50%, -50%);

/* 下面的设置无效 */
/* transform: translateY(-50%);
transform: translateX(-50%); */
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

需要注意的是,transform 属性只能设置一次,后面设置的会自动覆盖前面的,所以需要使用组合属性translate(-50%, -50%);且需要设置两个值,不能像 margin 或 padding 一样省略其他值,当然,也可以使用translate3d(-50%, -50%, 0);,看起来高大上,但没有实际意义;

方法四

日常很少使用,由方法三演变而来元素自身 position: relative; top: 50%; transform: translateY(-50%);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: relative;
top: 50%;
transform: translateY(-50%);
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

咦,position: absolute;还能理解,毕竟父元素设置了position: relative;,所以相对于父元素定位,给自身设置position: relative;是什么鬼?那么它相对于什么定位?top: 50%;又如何理解呢?

上面的方法中已经提到了,绝对定位相对于父元素 content 区域的原点定位,而相对定位则相对于父元素边框(border)最内侧定位,注意 ⚠️:不是相对于父元素的原点(左上角),而是边框内侧,这里差别很大,测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
position: relative;
}
.test {
width: 100px;
height: 100px;
background-color: #00f;
position: absolute;
top: 50%;
transform: translateY(-50%);
float: left;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: absolute;
top: 50%;
transform: translateY(-50%);
float: left;
}
</style>

<div class="outer">
<div class="test"></div>
<div class="inner"></div>
</div>

添加一个.test元素,同时,让两个元素设置相同(除了背景色),可以看到元素重叠了,只能看到.inner元素,说明它们定位的原点相同,所以才发生了重叠现象;

是不是float属性影响了position呢?将float替换成display: inline-block;后,结果一样;

接着测试将元素自身设置为position: relative;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
}
.test {
width: 100px;
height: 100px;
background-color: #00f;
position: relative;
top: 50%;
transform: translateY(-50%);
float: left;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: relative;
top: 50%;
transform: translateY(-50%);
float: left;
}
</style>

<div class="outer">
<div class="test"></div>
<div class="inner"></div>
</div>

这次,两个元素位置相同,但没有重叠,而是依次排列,已经说明问题了吧;

引出绝对居中,如下 👇:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
position: relative;
top: 50%;
/* 添加 left 属性设置,同时设置 X Y 轴移动 */
/* transform: translateY(-50%); */
left: 50%;
transform: translate(-50%, -50%);
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

方法五

还是常用方法之一,使用了 CSS3 新属性:父元素 display: flex; align-items: center;;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
display: flex;
align-items: center;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

没啥好说的,不熟悉 flex 属性的去 MDN 查看文档,align-items属性描述了元素在交叉轴上的对齐方式;

至于绝对中,justify-content则描述如何分配主轴元素之间及其周围的空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
display: flex;
align-items: center;
/* 添加以下设置 */
justify-content: center;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

方法六

由方法五演变而来,没怎么使用过:父元素 display: flex; 元素自身 align-self: center;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
display: flex;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
/* 区别在这里 */
align-self: center;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

还是 CSS3 的新特性,align-self会对齐 grid 或 flex 行中的元素,并覆盖已有的align-items的值;

绝对居中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
display: flex;
/* 添加以下设置 */
justify-content: center;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
align-self: center;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

方法七

CSS3 新特性,网格布局display: grid;,示例就不写了, 把方法五和方法六中的display: flex;换成display: grid;就 OK 了;

方法八

终于要到display: table-cell;了,一个古老的 CSS 特性;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
display: table-cell;
vertical-align: middle;
}

.inner {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

呃,不知道咋说,display: table-cell;属性让元素以表格单元格的形式呈现,类似于 td 标签,而vertical-align则用来指定行内元素(inline)或表格单元格(table-cell)元素的垂直对齐方式;

绝对居中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<style>
.outer {
width: 500px;
height: 500px;
background-color: #000;
display: table-cell;
vertical-align: middle;
/* 添加设置 1 */
text-align: center;
}

.inner {
/* 添加设置 2 */
display: inline-block;

width: 100px;
height: 100px;
background-color: #f00;
}
</style>

<div class="outer">
<div class="inner"></div>
</div>

是的,这里需要将元素转为行内(块)元素,因为text-align属性只能定义行内内容(例如文字)如何相对它的块父元素对齐,却并不能控制块元素自己的对齐;

方法九

一种障眼法,基本没有使用场景,添加一个隐藏元素,将要显示的元素挤到中间位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style>
.out {
width: 500px;
height: 500px;
background-color: #000;
}
.hidden {
height: 200px; /* (out - in) / 2*/
}
.in {
width: 100px;
height: 100px;
background-color: #00f;
}
</style>

<div class="out">
<div class="hidden"></div>
<div class="in"></div>
</div>

这玩意儿就不必说绝对居中了吧?😂

结束

暂时就想到这么多,以后学到了新知识再做补充;

以上~