解讀 CSS 布局之水平垂直居中
對一個元素水平垂直居中,在我們的工作中是會經常遇到的,也是CSS布局中很重要的一部分,本文就來講講CSS水平垂直居中的一些方法。由于我們大搜車的日常工作中已經不再需要理會低版本IE,所以本文所貼出的方法,是沒有去考慮IE的,如果有兼容需要,可以參見這篇文章: http://www.cnblogs.com/Dudy/p/4085292.html
先看一張圖,這是去年cssConf大會時阿里的 @寒冬winter 老師放出來的:
如圖所示,CSS布局是可以分為幾大塊的:
- 盒子內部的布局
- 文本的布局
- 盒模型本身的布局 </ul> </li>
- 盒子之間的布局visual formatting
- 脫離正常流normal flow的盒子的布局
- absolute布局上下文下的布局
- float布局上下文下的布局 </ul> </li>
- 正常流normal flow下的盒子的布局
- BFC布局上下文下的布局
- IFC布局上下文下的布局
- FFC布局上下文下的布局
- table布局上下文下的布局
- css grid布局上下文下的布局 </ul> </li> </ul> </li> </ul>
所有的CSS布局其實都是圍繞著這些布局模塊來的,水平垂直居中也一樣。
一. 文本的水平垂直居中
line-height + text-align:center
代碼:
<div class='wrap'> 水平垂直居中水平垂直居中 </div>
html,body{
margin: 0; }.wrap{ line-height: 400px; text-align:center;
height: 400px; font-size: 36px; background-color: #ccc; }</pre>
這種方法只適合單行文字的水平垂直居中
二. 利用盒模型的水平垂直居中
我們一般講的盒模型都是說的塊級盒的盒模型,也只有塊級盒的盒模型用得多一點,塊級盒block-level box又是分別由content-box、padding-box、border-box、margin-box組成的,如下圖:
也就說我任一個子盒子的水平和垂直方向的邊與最外面盒子的間距都是可以控制的,因此也就有如下居中方法:
padding填充
代碼:
<div class="wrap"> <div class="content"></div> </div>
@wrapWidth : 400px;
.wrap{ margin-left: auto; margin-right: auto; margin-top: 20px; width: @wrapWidth; height: @wrapWidth; background-color: #ccc; }
.content{ @contentWidth : 100px; width: @contentWidth; height: @contentWidth; padding: (@wrapWidth - @contentWidth) / 2; background-color: #333; background-clip:content-box; }</pre>
也可以用css3的calc()動態計算:
<div class="wrap"> <div class="content"></div> </div>
.wrap{ margin-top: 20px; margin-left: auto; margin-right: auto; width: 400px; height: 400px; background-color: #ccc; .content{ padding: -webkit-calc(~"(100% - 100px) / 2"); padding: calc(~"(100% - 100px) / 2"); width: 100px; height: 100px; background-color: #333; background-clip: content-box; } }
注意這里我在calc中使用了一個~""的寫法,這是less中的一個語法,告訴less這里不被less所編譯,要是被less變異了的話,css的calc函數的參數就不是100% - 100px,而是0%了。
margin填充
代碼:
<div class="wrap"> <div class="ele"></div> </div>
.wrap{ @wrapHeight : 400px; @contenHeight : 100px; overflow: hidden; width: 100%; height: @wrapHeight; background-color: #ccc; .ele{ margin-left: auto; margin-right: auto; margin-top: (@wrapHeight - @contenHeight) / 2; width: 100px; height: @contenHeight; background-color: #333; color: #fff; } }
使用margin填充我們需要知道元素的寬度,這點不太靈活,不過CSS3搞出了一個加fit-content的屬性值,可以動態計算元素的寬度, DEMO鏈接
使用盒模型進行布局不會產生reflow,兼容也好,使用盒模型布局是一種布局思想,其實僅僅靠它就能實現很多visual formatting才能實現的布局,這是另一個話題,這里不展開。
三. absolute布局上下文下的水平垂直居中
50% + -50%
原理很簡單,就是利用left:50%將盒子的左邊先置于父容器的中點,然后再將盒子往左偏移盒子自身寬度的50%,這里有三種具體實現:
<div class="wrap">
<div class="ele margin">水平垂直居中水平垂直<br>居中水平垂直居中水平<br>垂直居中水平垂直居<br>中水平垂直居中</div> </div><div class="wrap">
<div class="ele translate">水平垂直居中水平垂直<br>居中水平垂直居中水平<br>垂直居中水平垂直居<br>中水平垂直居中</div> </div><div class="wrap">
<div class="ele relative"> <div class="ele-inner">水平垂直居中水平垂直<br>居中水平垂直居中水平<br>垂直居中水平垂直居<br>中水平垂直居中</div> </div> </div></pre>.wrap{ position: relative; width: 100%; height: 200px; border:1px solid; background-color: #ccc; .ele{ position: absolute; left: 50%; top: 50%; background-color: #333; &.margin{ width: 160px; height: 100px; margin-left: -80px; margin-top: -50px; } &.translate{ -webkit-transform:translate3d(-50%, -50%, 0); transform:translate3d(-50%, -50%, 0); } .ele-inner{ position: relative; left: -50%; top: -50%; width: 100%; height: 100%; background-color: #333; } &.relative{ width: 150px; height: 100px; background-color: transparent; } } }
上面三個方法中,margin方法和relative方法都需要知道元素的寬高才行(relative方法只知道高也行),適用于固定式布局,而transform方法則可以不知道元素寬高
text-align:center + absolute
text-aign:center本來是不能直接作用于absolute元素的,但是沒有給其left等值的行級absolute元素是會受文本的影響的,可以參考張老師的 這篇文章
代碼:
<div class="wrap"> <div class="ele"></div> </div>
.wrap{ text-align: center;
width: 100%; height: 400px; background-color: #ccc; font-size: 0; } .ele{ position: absolute; margin-left: -(100px / 2); margin-top: (400px - 100px) / 2;
width: 100px; height: 100px; display: inline-block; background-color: #333; }</pre>
簡單解釋下,首先,text-align:center作用的是文本而不是absolute的元素,但是,當absolute元素為inline-block的時候,它會受到文本的影響,然后你可能會問這里沒文本啊,我只能告訴你說這下面是有的,是個匿名的文本節點。具體的這里不展開,可以參考 標準 ,然后理解這句話:
If the inline box contains no glyphs at all, it is considered to contain a strut (an invisible glyph of zero width) with the A and D of the element's first available font
然后這個匿名文本由于受到text-align:center影響居中了,這個時候absolute盒子的左邊跟父容器的中點對齊了,所以就還需要往回拉50%,這里用的是margin-left,你也可以用其它方式拉。然后就是垂直方向的對齊,垂直方向是不能被操作文本的屬性影響的,所以我這里用的是margin-top來讓它偏移下去。
absolute + margin : auto
代碼:
<div class="wrap"> <div class="ele"></div> </div>
html,body{ width: 100%; height: 100%; margin: 0; } .wrap{ position: relative; width: 100%; height: 100%; background-color: #ccc; .ele{ position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; width: 100px; height: 100px; background-color: #333; } }
關于這種布局的原理,在標準中能找到如下解釋:
w3c.org 中有這樣一句話:
The constraint that determines the used values for these elements is: 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right' = width of containing block
這句話說的是absolute性質的盒子,它的包含塊的寬度等于它的盒模型的寬度 + left + right值,包含塊的高度同理,盒模型包括margin-box、border-box、padding-box、content-box,而在這個居中方法中,.ele的left + right值是0,width是定值,width所在盒子包括了除了margin-box外的那三個box,margin都是auto值,按照上面那個公式,margin-left + margin-right的值應該等于包含塊的寬度 - left的值 - right的值 - width的值,也就是說margin-left + margin-right的值等于除了width所占寬度外的剩下寬度,擁有剩下寬度后,就是平分其寬度,以讓左右兩邊相等,達到居中,標準中給出了答案:
If none of the three is 'auto': If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra constraint that the two margins get equal values, unless this would make them negative, in which case when direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and solve for 'margin-right' ('margin-left')
結合原文中的上下文,這句話主要說的是如果left、right和width都不為auto,同時margin-left和margin-right都是auto,除非特別情況,它們倆就是相等的,而這個例子中不在特殊情況之列,因此兩者平分,此時達到了水平居中。而對于,垂直方向的margin的auto值的計算,我沒有找到相關規范,還望知道的大神予以指點。
這種方法能簡單的做到居中,但是必須有width和height值
適用于圖片居中的網易nec的一個方法
代碼:
<div class="wrap"> <p> <img src="https://simg.open-open.com/show/687602e311e0b26f512c1abab38514f0.jpg" alt="" /> <img src="https://simg.open-open.com/show/687602e311e0b26f512c1abab38514f0.jpg" alt="" /> </p> </div>
html,body{ width: 100%; height: 100%; margin: 0; } .wrap{ position:relative; width: 100%; height: 100%; p{ position:absolute; left:50%; top:50%; } img{ &:nth-child(1){ position:static; visibility:hidden; } &:nth-child(2){ position:absolute; right:50%; bottom:50%; } } }
這種方法主要是利用了一個圖片進行占位,以讓父容器獲得高寬,從而讓進行-50%偏移的圖片能有一個參照容器作百分比計算。優點是可以不知道圖片的大小,隨便放張尺寸不超過父容器的圖片上去都能做到居中。另外,兼容性好,如果是不使用nth-child選擇器的花,IE6都是能順利兼容的
四. float布局上下文下的水平垂直居中
float + -50%
代碼:
<div class="wrap"> <div class="ele"> <div class="ele-inner">居中居中居中居中居中居中<br>居中居中居中居中居中居中居中居中居<br>中居中居中居中居中居中居中居中居中居<br>中居中居中居中居中居中居中</div> </div> </div>
.wrap{ float: left; width: 100%; height: 400px; background-color: #ccc; .ele{ float: left; position: relative; left: 50%; top: 50%; } .ele-inner{ position: relative; left: -50%; -webkit-transform : translate3d(0, -50%, 0); transform : translate3d(0, -50%, 0); background-color: #333; color: #fff; } }
這種方法的原理,首先是利用float屬性將需要居中的元素的父元素.ele的寬度收縮,然后left:50%將.ele的左邊和水平中線對齊,這個時候還沒居中,還需要將其往回拉自身寬度的50%,于是.ele-inner便是真正需要水平居中的元素,我給它一個position:relative,將其往回拉自身寬度50%就行了。對于垂直方向,依然是先將.ele top:50%到垂直方向中點,但是這時給.ele-inner top:50%是不起作用的,因為如果沒給父元素明確高度的話,這個50%是計算不出來的,因此,就有了transform : translate3d(0, -50%, 0)。
這種方法的好處是元素可以不定寬,任何時候都可以做到居中
我當時在w3cplus的站上發現這個方法后,當時覺得這個方法很好,兼容性好,又還可以不定寬,但當我用了一段時間后,發現了一個問題:
就是當居中元素的父元素left:50%時,如果元素寬度足夠大,會超出外面的容器,而如果外面的容器又正好是overflow:auto的話,那就會在外面產生滾動條,問題DEMO鏈接 在這里 ,后來我找到了一個辦法: DEMO鏈接 ,基本思想就是利用元素超出父元素的左邊不會產生滾動條的特性,有點奇淫技巧,但是能解決問題,有興趣的可以看看
margin-bottom : -50%
代碼:
<div class="wrap"> <div class="placeholder"></div> <div class='content'></div> </div>
.wrap{ float: left; width: 100%; height: 400px; background-color: #ccc; @contentHeight : 100px; .placeholder{ float: left; width: 100%; height: 50%; /*居中元素.content高度一半*/ margin-bottom: -(@contentHeight / 2); } .content{ position: relative; left: 50%; transform:translate3d(-50%, 0, 0); clear: both; /*演示用,實際不需要定寬*/ max-width: 100px; height: @contentHeight; background-color: #333; } }
這種方法是先讓占位元素.placeholder占據50%高度,然后給一個居中元素高度一半的負的margin-bottom,然后下面的元素只要跟著擺放就能垂直居中了。水平方向就是利用translate做偏移,這個沒什么好說的,你也可以換成其他辦法。
這種方法就是各種固定死,首先最外層的父容器需要一個固定高度,以讓.placeholder的height:50%有效,然后,margin-bottom也需要固定死,而且得需要知道居中元素高度。單純就水平方向來說,這個方法比較適合需要兼容低版本IE的固定式布局的項目,因為兼容性好。
五.BFC布局上下文下的水平垂直居中
BFC的全稱是塊級排版上下文,這里有篇 文章 對齊進行了簡單的介紹,BFC布局上下文下的布局其實就是利用盒模型本身進行的布局,前面在利用盒模型布局的那一節中已經講過了,這里就不重復了
六.IFC布局上下文下的水平垂直居中
IFC又是個什么概念呢,你可以看看 官方文檔 ,也可以簡單的理解為 display為inline性質的行級元素的布局。
text-align:center + vertical-align:middle
代碼:
<div class="wrap"> <div class='placeholder'><!--占位元素,用來作為居中元素的參照物--></div> <div class="ele"></div> </div>
.wrap{ width: 100%; height: 400px; /* min-height: 400px; */ text-align:center; font-size: 0; background-color: #ccc; .placeholder, .ele{ vertical-align: middle; display: inline-block; } .placeholder{ overflow: hidden; width: 0; min-height: inherit; height: inherit; } .ele{ width: 100px; height: 100px; background-color: #333; } }
行級元素會受到text-align和vertical-align的影響,關于vertical-align,不太好理解,我多貼幾篇文章: @靈感idea 的 , 張鑫旭的 , MDN上的 , css-trick上的 ,以及 官方文檔 ,這里首先是用text-center讓inline-block水平居中,然后給一個vertical-align:middle,但是僅僅給vertical-align:middle是不夠的,因為此時它還沒有vertical-align對齊的參照物,所以就給了一個占位的inline-block,它的高度是100%。
這個方法對于居中元素不需要定寬高,而且元素根據vertical-align值的不同不僅僅可以實現居中,還可以將其放在上面下面等。缺點是父元素需定高
text-align:center + line-height
代碼:
<div class="wrap"> <div class="ele">居中居中居中居中居中居中<br>居中居中居中居中居中居中居中<br>居中居中居中居中居中居中居中居中<br>居中居中居中居中居中居中居中居中</div> </div>
.wrap{ text-align: center; line-height: 400px; width: 100%; height: 400px; background-color: #ccc; font-size: 0; .ele{ line-height: normal; vertical-align: middle; display: inline-block; background-color: #333; font-size: 18px; color: #fff; } }
這個方法,首先是水平方向,text-align:center就行了,至于垂直方向,起作用的就是父容器的一個line-height和居中元素的vertical-align:middle,為什么這兩個屬性可以讓一個inline-block垂直居中呢,這里重點是父容器在其下面產生了一個隱匿的文本節點,這個我在上面 text-align:center + absolute 那個方法的講解中說到過了,然后這個這個隱匿文本節點會因line-height屬性的作用而擁有了一個父容器一樣高的行高,此時元素有了一個vertical-align對齊的參照物,再給其vertical-align:middle值就能垂直對齊了。
使用這個方法,居中元素無需定寬高,但缺點是得給父容器一個固定的行高才行。
text-align:center + font-size
代碼:
<div class="wrap"> <div class="ele"></div> </div>
.wrap{ text-align: center; font-size: 400px * 0.873;/*約為高度的0.873*/ margin-left: auto; margin-right: auto; width: 400px; height: 400px; background-color: #ccc; .ele{ vertical-align: middle; width: 100px; height: 100px; display: inline-block; background-color: #333; } }
這個方法來自淘寶,基本原理還是讓隱匿文本節點所占據的行高等于父容器的高度,然后給居中元素一個vertical-align:middle對齊的一個參照。只是這里把定義line-height值換成了定義font-size值,讓font-siz足夠大從而讓其行高等于父容器高度。為了證明這個font-size的作用,我把居中元素換成文本
代碼:
<div class="wrap"> a </div>
.wrap{ text-align: center; font-size: 400px * 0.873;/*約為高度的0.873*/ margin-left: auto; margin-right: auto; width: 400px; height: 400px; background-color: #ccc; }
效果:
可以看到字母a垂直居中了,這個字母a就對應那個隱匿文本節點
七.FFC布局上下文下的水平垂直居中
代碼:
<div class="wrap"> <div class="ele"> 居中居中居中居中居中居中居中<br> 居中居中居中居中居中居中居中<br> 居中居中居中居中居中居中居中<br> 居中居中居中居中居中居中居中<br> 居中居中居中居中居中居中居中 </div> </div>
html,body{ width: 100%; height: 100%; } .wrap{ display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: #ccc; .ele{ background-color: #333; } }
flexbox是個很強大的布局模塊,也就三個屬性就搞定居中了,而且不論父容器還是居中元素都可以不定寬高。參考鏈接: 圖解CSS3 Flexbox屬性
八.table布局上下文下的水平垂直居中
代碼:
<div class='wrap'> <div class='ele'> <div class="ele-inner">居中居中居中居中居中居中居中居中<br>居中居中居中居中居中居中居中居中<br>居中居中居中居中居中居中居中居中居中居中</div> </div> </div>
.wrap{ width: 100%; height: 300px; display: table; background-color: #ccc; } .ele{ text-align:center; vertical-align: middle; display:table-cell; } .ele-inner{ display: inline-block; background-color: #333; }
原理就是把div模擬成表格(換成真正的表格標簽也是可以的),然后給那幾個屬性就成了,這個沒什么好講的,不懂的去翻翻手冊就明白了,然后@于江水寫的一篇 table那些事 還不錯
九.CSS grid布局上下文下的水平垂直居中
CSS3 grid layout是IE搞出來的一個布局模塊,目前貌似還只有IE0和IE11支持,我沒有研究過其居中的方法,有興趣的可以看看 大漠老師的介紹文章
十.其它未知歸屬的水平垂直居中方法
使用button標簽
代碼:
<button> <div> 居中居中居中居中居中居中<br> 居中居中居中居中居中居中<br> 居中居中居中居中居中居中<br> 居中居中居中居中居中居中<br> </div> </button>
button{ width: 100%; height: 400px; background-color: #cccccc; border-width:0; &:focus{ outline:none; } div{ display: inline-block; font-size: 18px; background-color: #333; color: #fff; } }
這種方法屬于奇淫技巧,利用button標簽天生外掛的這一技能對其里面的元素進行居中。
(本文完)
- 脫離正常流normal flow的盒子的布局