vertical-align属性与垂直居中

让元素居中对齐是非常常见的需求,首先是水平居中,要实现水平居中行内元素只需要在其父元素上设置text-align: center即可,对于块级元素来说让它的margin-left: automargin-right: auto即可(width不可为auto),那么垂直居中呢?找下css属性发现了vertical-align,感觉就是它了,设置个vertical-align: middle,怎么没有达到预期效果?下面来详细介绍下vertical-align这个属性以及实现垂直居中的若干方法。

vertical-align属性是干什么的

根据W3C Spec中对vertical-align属性的定义:

This property affects the vertical positioning inside a line box of the boxes generated by an inline-level element.

什么是line box?同样来自W3C Spec

The rectangular area that contains the boxes that form a line is called a line box.

这个属性仅影响了单行中行内元素的垂直位置,那么我们会涉及到的元素应该是这样的:

  • inline

  • inline-block

既然我们要垂直居中,垂直居中是相对于垂直高度而言的,然而我们知道height对inline元素无效,那么line box的高度是怎么计算的呢?(还是引用W3C Spec)

The height of a line box is determined by the rules given in the section on line height calculations.

一个line box的高度是的计算方式如下:line box中的每一个行内元素都将加入到计算过程,如果是元素inline的则取其line-height的值,如果元素是inline-block或者是inline-table则取其margin-box的高度,最后这些值的最大值,即为line box的高度了。

好了,然后来看看vertical-align属性可以取哪些值:

| 可取值 | 说明 |

|———-|———|

| baseline|将元素的基线与父元素的基线对齐|

| middle|将元素的中线与父元素的基线加上x一半的高度对齐|

| sub|将元素置于基线下方合适的位置|

| super|将元素置于基线上方合适的位置|

| text-top|将元素的顶部与父元素正文区域的顶部对齐|

| text-bottom|将元素的底部与父元素的正文区域的底部对齐|

| top|将元素的顶部与line box顶部对齐|

| bottom|将元素的底部与line box底部对齐|

| < percentage >|基于基线上(正值)下(负值)移动元素,值通过百分比乘上行高而得|

| < length >|基于基线上(正值)下(负值)移动元素|

这里有两个概念:基线(baseline)和正文区域(content area)。

先来感受下,举个栗子:

https://jsfiddle.net/leozdgao/y4oexshn/6/

在第一个栗子中,直观地展示了vertical-align属性所有可取属性的表现。

在第二个栗子中,蓝色的线表示的就是基线(记得小时候英语练习本每行的第三根线么),绿色的轮廓表示的就是正文区域,这里故意加了个较大的行高,于是红色的轮廓表示的就是line box的轮廓了。

对于有正文的行内元素而言,它的基线正如上面的栗子中所展示的那样,那么对于没有正文的行内元素(这里指的是有大小的inline-block元素但没有正文或者类似与img video这样的replaced element),它们的基线位于它们margin box的底部。

我们发现在这些可取的值中,大部分都与它们的父元素(通常为line box)的基线有关,那么line box的基线在哪里?或者栗子里的蓝线是根据什么画出来的?在W3C Spec中是这样说的:

CSS 2.1 does not define the position of the line box’s baseline

是的,没有定义……不过好在浏览器之间的实现似乎没有什么区别,取的就是正常的一个文本的基线位置,这里就不再多纠结line box的基线是怎么计算的了。

这里额外说明下vertical-align: middle,根据上面的解释,什么是父元素的基线加上x一半的高度?还是来看个直观的栗子:

https://jsfiddle.net/leozdgao/hf3ocgd8/2/

蓝色的线依然表示基线,橙色的线表示的就是所谓的line box的基线加上x一半高度的位置。同时也展示了为什么仅给元素设置vertical-align: middle是没有办法达到效果的。

不过line box的基线并不是固定不动的,这次用一个实际的栗子来解释,比如在单行中有一张图片和一段文本,我们的需求是让文字垂直居中对齐:(我用一个inline-block的方块来模拟一张图片)

https://jsfiddle.net/leozdgao/pe8t1LLf/1/

第一个case是没有设置vertical-align时的样子,我们现在知道将文字部分设置vertical-align: middle是不正确的。在第二个case展示了在图片上应用vertical-align: middle,我们发现,为了满足图片中线与line box基线加上半个x高度的位置对齐,而图片位置不能移动了(整个line box就是它撑起来的),所以line box的基线被调整了。那么现在看上去好像是居中了?其实还是差一点,如case3中那样,这时将vertical-align: middle应用与文本上,就可以垂直居中了。

由此我们得到的结论是:对于那些直接影响着line box高度的行内元素来说,vertical-align对元素本身可能无影响,但会调整line box的基线。

垂直居中的若干种方法

垂直居中的需求往往并不限于单行文本,可能会设计多行文本,或者多行文本配图片等等,下面整理的各种垂直居中的方案,并试着分析其优劣。

方法一:

line-height设置为和height一样高,常用于导航栏或者标签页这样的单行文本居中,缺点是这种方法只能用于单行文本,如果还涉及到图片,可以根据上面一部分中提到的方法调整。

https://jsfiddle.net/leozdgao/150et323/

方法二:

利用css table,设置元素table结构,并应用vertical-align: middle实现垂直居中,这种方法的实现可用于多行文本,要求IE8及以上版本。

http://jsfiddle.net/leozdgao/00dgdbem/

方法三:

上下设置等高的padding或者margin值,个人觉得最笨的一种方法,唯一的优点是兼容所有浏览器,缺点是很不灵活,大小需要额外计算并写死。(不提供栗子)

方法四:

绝对定位,这种方法并不仅限于『居中』,一种是利用负的margin值来实现,另一种是用CSS3的translate来实现,优点的话同样是可以支持多行文本,只是负的margin会写死大小,仅适合与大小固定的元素,用translate要考虑css3的浏览器兼容问题,最大的局限性是绝对定位本身导致元素脱离文本流normal flow,多数情况下其父元素仅包括它一个子元素时会使用。

http://jsfiddle.net/leozdgao/h34zqLf2/1/

补充:支持CSS3的浏览器,也可以使用calc,像这样使用:

1
2

top: calc(50% - (300px / 2));

方法五:

同样是绝对定位,这个方法一般被称为Stretching,把4个定位方向全部设置为0,这种方式不会将大小写死,浏览器兼容性也很不错,但绝对定位的弊端上面也已经提到过了。

http://jsfiddle.net/leozdgao/5a9z676h/1/

这里简单解释下实现原理:我们发现了margin: auto,是的,我们给4个方向全部设置为0,即为元素提供了与其父元素相同的外边缘。接下来看W3C Spec中提到的:

If none of the three are ‘auto’: If both ‘margin-top’ and ‘margin-bottom’ are ‘auto’, solve the equation under the extra constraint that the two margins get equal values.

这是就垂直方向而言的:其中的three指的是top height bottom,就是margin值的计算结果会让元素尽可能居中。水平方向同理。

方法六:

隐藏一个浮动元素,这个浮动元素占父元素一半的高度,然后需要垂直居中的元素设置清除浮动,并设置大小为其高度一半的负margin-top,这个方法兼容任何浏览器,但总感觉有点hack的意味,不推荐。

http://jsfiddle.net/leozdgao/zoft69ao/1/

方法七:

FlexBox布局,浏览器需要支持,在移动端可大肆使用。

http://jsfiddle.net/leozdgao/4my89br7/1/

css