44年前我們把人送上月球,但在CSS中我們?nèi)匀徊荒芎芎脤崿F(xiàn)垂直居中
讓一個元素水平居中對于CSS來說非常簡單:如果是一個內(nèi)聯(lián)元素,我們可以在他的父元素上設(shè)置text-align:center;如果是一個塊元素,我們可以使用margin:auto;然而,只要一想到讓一個元素垂直居中,讓人死的心都有了。
多年來,垂直居中已成為CSS的不朽神話,也是前端專業(yè)人士群體中的一個內(nèi)部笑話。原因是:
1)經(jīng)常需要使用
2)理論上看上去非常簡單
3)過去實戰(zhàn)中要實現(xiàn)是極其困難,特別是元素大小固定時
前端開發(fā)人員多年來盡所能的來解決這個問題,共中最令人不安的是使用了很多Hack手段。在這一節(jié)中,我們一起探索一些實現(xiàn)垂直居中現(xiàn)代技術(shù)。注意,有一些受歡迎的技術(shù),不在這里討論,主要是因為:
表格布局不討論(表格顯示模式),因為它需要一些冗余的HTML標簽
inline-block方法不包括,因為要使用很多Hack手段
然而,如果你對這方面感興趣,你可以閱讀Chris Coyier寫的博文《Centering in the Unknown》,里面介紹了這兩種技術(shù)。
如果沒有特殊聲明,后面示例用的HTML結(jié)構(gòu)都是在<body>
元素中插入下面的標簽
<main>
<h1>Am I centered yet?</h1>
<p>Center me, please!</p>
</main>
為了得到下圖所示效果,我們也應(yīng)用一些基本的CSS樣式,比如background
、padding等等:

絕對定位解決方案
最早實現(xiàn)垂直居中的技術(shù)是元素需要一個固定的寬度和高度:
main {
position: absolute;
top: 50%;
left: 50%;
margin-top: -3em; /* 6/2 = 3 */
margin-left: -9em; /* 18/2 = 9 */
width: 18em;
height: 6em;
}
從本質(zhì)上講,它將元素的左上角點移到視窗中心點,然后使用負的margin(使用margin-top和margin-left),而且margin的值是元素寬度和高度的一半,使元素的中心點與視窗的中心點重合。如果使用calc()
可以減少兩個樣式:
main {
position: absolute;
top: calc(50% - 3em);
left: calc(50% - 9em);
width: 18em;
height: 6em;
}
顯然,這種方法最大的問題是,元素需要一個固定的尺寸,而需要垂直居中元素的尺寸常常是需要由它的內(nèi)容來決定。如果我們有辦法使用百分比來控制元素尺寸,我們的問題就解決了。不幸的是,對于大多數(shù)CSS屬性(包括margin)百分比的值是相對于其父元素的寬度來決定。
在CSS中常??梢钥吹胶芏嘟鉀Q方案是不可思議。在這個例中,就可以使用CSS3的transform??梢栽?code>transform中的translate()使用百分比,讓元素移動是相對于自身的寬度和高度,這種方案正是我們需要的。
main {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
當然,沒有哪門技術(shù)是完美的,在實際使用中要注意一下以下幾個事項:
- 絕對定位通常不是一個很好的選擇,因為它對整體的布局影響相當?shù)拇蟆?/li>
- 如果垂直居中元素的內(nèi)容比視窗高度更高的話,它的頂部會被裁剪掉,如下圖所示。但是這個問題可以解決,只不過需要使用一些
Hack手段。 - 在一些瀏覽器中,可能會導(dǎo)致元素出現(xiàn)略微的模糊,那是因為元素有可能被放置在半個像素位置上。我們可以通過
transform-style:preserve-3d來解決,盡管這是一個Hack手段,不能保證它不會過時。

視窗單位的解決方案
如果想避免使用絕對定位,我們?nèi)匀豢梢允褂?code>translate()方法,其值剛好是元寬度和高度的一半。然而,我們?nèi)绾尾皇褂?code>top和left將元素從top和left移動50%的偏移量呢?
首先想到的是給margin屬性一個百分數(shù),像這樣:
main {
width: 18em;
padding: 1em 1.5em;
margin: 50% auto 0;
transform: translateY(-50%);
}
然而,正如你所看到的效果,如下圖所示:

這產(chǎn)生了非常奇怪的效果。主要原因是margin的百分比計算是相對于父容器的width來計算。是這樣的,甚至包括margin-top和margin-bottom
值得慶幸的是,如果我們想讓元素在視窗中居中,還是有希望的。CSS Values and Units Level 3定義了一種新的單位,稱為相對視窗(viewport-relative)長度單位。vw是相對于視窗的寬度。與你預(yù)期剛好相反,1vw相當于視窗寬度的1%,而不是100%
與vw相似的是,1vh相當于視窗高度的1%
如果視窗的寬度小于高度,1vmin等于1vw,反之,如果視窗寬度大于高度,1vmin等于1vh
如果視窗的寬度大于高度,1vmax等于1vw,反之,如果視窗寬度小于高度,1vmax等于1vh
在這個示例中,我們需要給margin的值設(shè)置vh單位:
main {
width: 18em;
padding: 1em 1.5em;
margin: 50vh auto 0;
transform: translateY(-50%);
}
正如你看到效果很完美。

當然,這種方法有用性是有極限的,因為它只適用于元素在視窗中垂直居中。
請注意,還可以使用相對視窗單位來創(chuàng)建全屏效果,而且不需要使用任何腳本。更多細節(jié)可以閱讀Andrew Ckor寫的《Make full screen sections with 1 line of CSS》博文。
Flexbox的解決方案
這無疑是最好的解決方案,因Flexbox的出現(xiàn)就是為了解決這樣的問題。其他解決方案仍然可用,唯一原因是他們能更好的在瀏覽器上呈現(xiàn),不過Flexbox在現(xiàn)代瀏覽器也得到更好的好支持。
只需要兩個樣式,在需要垂直居中的父元素上設(shè)置display:flex
(這個示例中就是<body>)和在垂直居中的元素上設(shè)置margin:auto(這個示例中就是<main>):
body {
display: flex;
min-height: 100vh;
margin: 0;
}
main {
margin: auto;
}
注意,當使用Flexbox和margin:auto時,元素不僅水平居中,而且也會垂直居中。也注意,我們甚至沒有設(shè)置寬度(如果我們想要也可以設(shè)置),其實指定的寬度相當于max-content。
如果瀏覽器不支持Flexbox,那么結(jié)果看起來就會像下圖(如果我們給元素設(shè)置了寬度):

即使不是垂直居中,還是可以接受的。
Flexbox的另一個優(yōu)點是,可以讓匿名容器垂直居中。例如,我們將結(jié)構(gòu)換成:
<main>Center me, please!</main>
我們可以通過align-items和justify-content屬性使設(shè)置固定尺寸的<main>容器里面的文本居中。如下圖所示:

我們可以在<body>和需要居中的元素<main>使用相同的屬性,同時使用margin:auto做為備用,以于優(yōu)雅降級。
main {
display: flex;
align-items: center;
justify-content: center;
width: 18em;
height: 10em;
}
新特性:對齊所有東西
CSS Box Alignment Level 3已經(jīng)在計劃,在未來我們甚至不需要使用不同的布局模式就能非常容易的實現(xiàn)垂直居中,我們只需要像下面這樣做:
align-self: center;
不管元素上使用其他樣式,這個將來都能運行。這聽起來令人難以置信,但將來在瀏覽器中是可以渲染的。