42

本文在evernote里有备份。如果evernote的阅读区域嫌窄了,那么可以把这个链接拖入书签并点击javascript:jQuery("#container").width(980);


本文从这个回答整理而来。对于当今出现的一些CSS垂直居中的方案,这篇文章将会系统地审视它们,从实用角度进行评估。

不罗嗦,先上图:

请输入图片描述


考量需求及难度

为避免混淆,本文中所说的父容器子容器都不是相对的,而对应着如下文档结构里面的.outer.inner容器:

<div class="outer"> <!-- 父容器 -->
     <div class="inner"> <!-- 子容器 -->
          ... <!-- 子容器内部内容 -->
     </div>
     ... <!-- 父容器内其他的子元素 -->
</div>

我们从如下角度来评估:

  • 父容器/子容器的高度是否可变
  • 是否需要手工计算
  • 子元素溢出时父元素是截断、被撑高还是保留滚动条
  • 当然,还有兼容性

高度相关

代码 效果
height:100px; 定高
height:auto 不定高,自适应于自身的content-box
height:50% 不定高,自适应于包含块

出现了 3(定高/自适应于外部/自适应于内部) ^ 2 (父容器/子容器) =9 种情形,搭配见下:

父容器 子容器 难度 解(tu)说(cao)
定高 定高 简单 快速开发时就是这样
定高 自适应于内部 稍难 常见的需求
定高 自适应于外部 简单 手工算一算就好了
自适应于内部 定高 简单 变相定高,高度放在内层,方便解耦
自适应于内部 自适应于内部 稍难 留白固定,罕见的需求
自适应于内部 自适应于外部 WTF 折腾出这种需求,不觉得害臊吗
自适应于外部 定高 困难 适配所有屏幕的slideshow
自适应于外部 自适应于内部 困难 普适性更高的slideshow
自适应于外部 自适应于外部 稍难 一个模块(或大小不定的头像),出现在大小不定的位置

溢出相关

子容器溢出代表子容器高度大于父容器高度的情形。

  • 子容器溢出时,被父容器截断。父容器overflow:hidden
  • 子容器溢出时,把父容器撑高。父容器height:auto;overflow:visibledisplay:table-cell等等。
  • 子容器溢出时,父容器出现滚动条。父容器overflow:scrolloverflow:auto

很显然,子容器溢出时,被父容器截断的情形无法和父容器自适应于子容器共存。

兼容性

  1. IE6、IE7:老而不死的浏览器,浏览器尚未统一、IE一家独大之时的遗毒,一大堆bug等着你。
  2. IE8:IE8起全面支持CSS2.1,剩下一些稍微少坑爹那么一点点的bug。
  3. IE9。尴尬的产物,微软第一次搭上CSS3的末班车。
  4. IE10+与其他现代浏览器(终于可以和其他浏览器并列了……IE10泪目)。

4、3、2、1的兼容难度是一步步变难,兼容到4、3、2、1所对应的代码量/工作量是100%、102%、120%、300%的关系。

在这里仅仅考量IE6/7,IE8的无bug实现的兼容性。IE9+的兼容性,对于文中提到的所有方案都是可行的。

其他

  1. 是否需要手动计算/需要calc属性进行辅助计算

如果需要手动计算,若界面进行重构,而居中的需求不变,就需要重新计算。比较费时费事。布局也相对不够灵活。


方案汇总

CSS 版本 布局类型 方案
CSS 2.1 普通流,块级布局 父容器上下等padding
子容器上下等margin
普通流,行内布局 父容器line-height=height
子容器所在的line-box的line-height=父容器的height
普通流,块级table布局 父容器display:table-cell,子容器vertical-align
子容器被table、tr、td包裹
绝对定位布局 子容器绝对定位,top:50%,负margin
子容器绝对定位,top:0,bottom:0,margin:auto
普通流,块级布局 background代图片background-position
CSS3 绝对定位布局 子容器绝对定位,top:50%,translateY(-50%)
普通流,块级布局 backgournd代图片backgournd-size
普通流,flexbox布局 flexbox

方案评估

1. 普通流,块级布局 (css2.1)

方案 兼容性 父容器 子容器 子容器溢出 其他
IE8 IE6/7 定高 自适应于内部 自适应于外部 定高 自适应于内部 自适应于外部 撑高 被截断 不需手工计算
父容器上下等padding √* × √* × √* ×
子容器上下等margin √* × √* × × ×
background代图片background-position × × ×

1.1. 父容器上下等padding & 子容器上下等margin

最基础的方案就是这样,手工算好每一个容器的高度和补白/位移需要的内容,简单粗暴:

/*父子均定高,父容器上下等padding*/
.outer{ height: 100px; padding-top: 40px ; }
.inner{ height: 100px; }
/*父子均定高,子容器上下等margin*/
.outer{ height: 180px; overflow: hidden; *zoom: 1; }/*父容器给予BFC以避免子容器margin并到父容器上*/
.inner{ height: 100px; margin-top: 40px ; }

父子都需要自适应于内部时:

/*父子均自适应于内部,父容器上下等padding*/
.outer{ height: auto; padding-top: 40px ; padding-bottom: 40px ; }
.inner{ height: auto; }
/*父子均自适应于内部,子容器上下等margin*/
.outer{ height: auto; overflow: hidden; *zoom: 1; }/*父容器给予BFC和haslayout,以避免子容器margin并到父容器上*/
.inner{ height: auto; margin-top: 40px ; margin-bottom: 40px ; }

说明:

  1. 需求:父子都需要自适应于内部的解决方案里,是把留白的高度固定,这种需求其实很少见。
  2. 可缺省:height:auto可以省去,这是默认值。
    对于块级元素,默认是height:auto;width:auto;
    width的auto值是自适应于这个元素的包含块的宽度,而height自适应于这个元素的content-box的高度

  3. 自适应:父子容器均可以自适应于内部;也可以父容器自适应,子容器定高。
    因为height:auto时,计算高度值的依赖方向是从外往内。

  4. 自适应:为何在这俩方案里不容许出现父容器或子容器自适应于外层呢?
    因为对于height来说,它的百分比值是乘以包含块的height,但padding-top/padding-bottom/margin-top/margin-bottom的百分比值是乘以包含块的width,而非我们希望的height。

  5. 溢出:

* 父容器上下等padding时,父容器定高时,子容器溢出padding-edge的部分会被截断。
* 子容器上下等margin时,父容器自适应于内部,父容器只会被撑高,永远无法被截断。
* 其他情形,截断都没有什么意义

1.3. background代图片background-position

在子容器是<img>标签时,可以直接使用background来替代它。也可以算得上一种方案。

.inner { background-image: url("...");
   background-position: 50% 50%;/*注,火狐不支持background-position-y的属性设置*/ }

说明:

  1. 自适应:无法自适应于外部容器,CSS2.1阶段,background无法相对于容器伸缩。

  2. 溢出:背景溢出的情形,直接会被截断。

  3. 其他:你甚至可以把inner的标签省掉,直接把背景放到outer之上

2. 普通流,行内布局 (css2.1)

方案 兼容性 父容器 子容器 子容器溢出 其他
IE8 IE6/7 定高 自适应于内部 自适应于外部 定高 自适应于内部 自适应于外部 撑高 被截断 不需手工计算
父容器line-height=height √* × × × ×
子容器所在的line-box的line-height=父容器的height √* √* × ×

2.1 父容器line-height=height

“父容器line-height=height”,这个方案在于将子容器当做行内元素呈递,并设置vertical-align,line-box和block-box的高度持平,就完成了垂直居中的的效果。

.outer { line-height: 100px; height: 100px; font-size: 0; }
.inner { display:inline-block; vertical-align:middle; font-size: 16px; }

为何需要font-size:0?因为vertical-align:middle的定义是:元素的中垂点与父元素的基线加1/2父元素中字母x的高度对齐。因此在font-size>0时,元素将会在baseline上出现一定的偏移,偏移量跟这个字号下的x字母的高度有关。

可以在jsFiddle中看到对比:

不使用font-size:0

http://jsfiddle.net/humphry/haaaM/

使用font-size:0

http://jsfiddle.net/humphry/7zCEm/

说明:

  1. 兼容性:没有涉及relative和absolute定位,布局时相对无痛。

  2. 兼容性:这个方案是IE6\7不支持的,原因不详(只知道inline-blockvertical-align的标准诞生于IE8之后),也没有时间研究。2014年了,有点追求好吗。

  3. 自适应:子容器可以轻松做到自适应于内部、外部或者定高,但在这个方案里,父容器必须定高,因为line-height的百分比单位是相对font-size来说的。

  4. 溢出:子容器溢出时会变成顶对齐,这是因为,line-box 的高度跟内部最高的 inline-box 相等,因而line-box 可以被撑高,从上往下排一个个排列下来,从而失去了居中的效果。换句话说,子元素溢出时,父容器可以自适应于内部。

  5. 其他:line-heightfont-size是一个可以继承的属性,在这种方案里面,必然会导致line-heightfont-size被继承,因此在需要排版子容器时,需要复写line-heightfont-size

2.2 子容器所在的line-box的line-height=父容器的height

对上面的方法不兼容IE6\7且不能做到父容器自适应的方面,可以这样改进:

  1. 把子容器当成行内元素呈递
  2. 构建一个行内级别的钩子元素,紧挨着子容器,以使用:before/:after伪元素自制一个文本节点、或<span>或任意一个inline级别的标签、或者一个1*1的图片。
  3. 让钩子元素撑满容器高度,这意味着它需要成为inline-box元素,然后设置height:100%即可。
  4. 现在,子容器所在的 line-box 的 line-height = 父容器的 height。
  5. 最后,给子容器和钩子元素设置vertical-align:middle,让它们的中心线对齐于于父容器的middle-line,就能做到垂直居中。

http://jsfiddle.net/humphry/86dsC/21/

现在这个布局可以自动生成,详见@林小志的css小工具:图片垂直居中span

(如果需要水平居中,那么这个额外的节点,需要移去自身所占的距离,一般使用margin-left:-1pxmargin-right:-1px,取决于钩子在子容器的哪一边。)

说明:

  1. 兼容性:没有涉及relative和absolute定位,布局时相对无痛。

  2. 兼容性:inline-box标签之间的任意数量的空白符:ASCII 空格 (&#x0020;)、ASCII 制表符 (&#x0009;)、ASCII 换页符 (&#x000C;)、零宽度空格 (&#x200B;) 会被浏览器解析成一个空格,造成间隙。需要通过改变HTML结构/使用负margin等来去掉这个间隙

  3. 兼容性:这个方案有一定hack量,去掉inline-box的空格间隙是一部分,display:inline-block的IE6\7 hack是另一部分。

  4. 自适应:父容器可以自适应于外部了,因为这里不需要在任何地方知道父容器的高度。

  5. 溢出:子容器溢出时会变成顶对齐,原因同上。

3. 普通流,块级table布局 (css2.1)

方案 兼容性 父容器 子容器 子容器溢出 其他
IE8 IE6/7 定高 自适应于内部 自适应于外部 定高 自适应于内部 自适应于外部 撑高 被截断 不需手工计算
父容器display:table-cell,子容器vertical-align × × × ×
子容器被table、tr、td包裹 √* √* ×

3.1. 父容器display:table-cell,子容器vertical-align

这个方案依然是用到vertical-align:middle,只不过需要放到作为display:table-cell的元素之上。

http://jsfiddle.net/humphry/7AMF9/2/

这个布局也可以自动化生成:见@林小志的css小工具:图片垂直居中 table cell

说明:

  1. 兼容性:IE6/7不兼容display:table-cell

  2. 自适应:子容器无法自适应于父容器,高度无法使用百分比单位,因为根据渲染规则,display:table-cell的元素的包含块是它父级的display:table的元素。

  3. 溢出:父元素就算给定高度,设置overflow,也不会导致溢出隐藏;在子元素溢出的时候,父容器不能保有自身设置的高度,直接会被撑高。

  4. 其他:display:tabel-cell本身让很多属性无效。

3.2. 子容器被table、tr、td包裹

为了可以让子容器有百分比高度,我们可以直接构建一个表结构出来:

http://jsfiddle.net/humphry/Ns4RK/8/

说明:

  1. 兼容性:这是一个全兼容的方案。

  2. 自适应:现在“父级”的自适应要求都可以得到满足,只不过这里的“父级”指的是包在最外层的table

  3. 溢出:父元素就算给定高度,设置overflow,也不会导致溢出隐藏。

  4. 其他:文档结构变复杂了,语义被抛弃了。

  5. 其他:display:tabel-cell本身让很多属性无效。

  6. 其他:可以把这个方案里的<table>换成display:table的其他元素,tr、td亦然:
    http://jsfiddle.net/humphry/KxKc8/
    个人觉得……徒增烦恼尔。

4. 绝对定位布局 (css2.1)

方案 兼容性 父容器 子容器 子容器溢出 其他
IE8 IE6/7 定高 自适应于内部 自适应于外部 定高 自适应于内部 自适应于外部 撑高 被截断 不需手工计算
子容器绝对定位,top:50%,负margin √* × × × ×
子容器绝对定位,top:0,bottom:0,margin:auto × √* × ×

4.1 子容器绝对定位,top:50%,负margin

.outer{ position: relative; }
.inner{ position: absolute; top: 50%; height: 20px; margin: -10px; }

这是互联网上能找到的最多的关于垂直居中的方法。我们不相信浏览器,使用手算,将子元素挪去自身高度的50%。

说明:

  1. 兼容性:子元素和父元素都需要设置position,这就意味着IE6\7下面的数十个友情附赠的美好bug。

  2. 自适应:子元素必须定高,不定高算不出来负margin。不可以是百分比高度单位,因为margin-top的百分比,是相对其包含块的宽度而言的。

  3. 自适应:父容器可以自适应于内部,只不过不是这个子元素,而是父容器内部其他的元素。子元素对外层高度、宽度塌陷,不能撑宽/撑高父容器了。

  4. 溢出:这个方案也可以支持子元素高度溢出的情形。

  5. 其他:需要手动计算子元素1/2的高度是多少。

4.2 子容器绝对定位,top:0,bottom:0,margin:auto

.outer{ position: relative; }
.inner{ position: absolute; margin-top: auto; margin-bottom : auto;
    top: 0; bottom: 0; height: 20px; }

这个的原理写在CSS2.1中:

‘top’ + ‘margin-top’ + ‘border-top-width’ + ‘padding-top’ + ‘height’ + ‘padding-bottom’ + ‘border-bottom-width’ + ‘margin-bottom’ + ‘bottom’ = 包含块的高度

在其他值不是auto的时候,margin-topmargin-bottom是可以根据上式算出的,原理类似于水平居中。

说明:

  1. 兼容性:这个方案仅仅支持IE8+。IE6和IE7由于对同时定义topbottom属性的样式解析与 css2.1 不一致,不支持这种定位方式。

  2. 自适应:父容器可以自适应于内部,只不过不是这个子元素,而是父容器内部其他的元素。原因同上。

  3. 自适应:这个方案需要子容器有一个固定的高,或百分比自适应于外部。它的高度不能是height:auto,因为这样会使得上面的算式里auto出现在三个地方,浏览器无法计算出相应margin值。

  4. 溢出:这个方案也可以支持子元素高度溢出的情形。

  5. 其他:完全不用算,耶!

5. css3一系列布局

方案 兼容性 父容器 子容器 子容器溢出 其他
IE8 IE6/7 定高 自适应于内部 自适应于外部 定高 自适应于内部 自适应于外部 撑高 被截断 不需手工计算
子容器绝对定位,top:50%,translateY(-50%) × × √* ×
backgournd代图片backgournd-size × × √* ×
flexbox × ×

5.1 子容器绝对定位,top:50%,translateY(-50%)

.outer{ position: relative; }
.inner{ position: absolute; top: 50%; transform: translateY(-50%);}

原理同上,仅仅是仅仅是用translate替换了负margin,因为translate的百分比偏移量是容器本身的。

说明:

  1. 兼容性:ie9+(但,从好处来讲,其实这个兼容性已经完全不需要考虑IE6~8的相对/绝对布局bug了)

  2. 自适应:子元素可以不指定高度,也可以相对父级高宽做百分比设置,也包括定宽,非常灵活

  3. 自适应:父容器可以自适应于内部,只不过不是这个子元素,而是父容器内部其他的元素。原因同上。

  4. 溢出:支持子元素溢出隐藏,或者溢出显示。

  5. 其他:你会让代码陷入一个前缀的海洋……

.inner{
    -webkit-transform:translate(-50%, -50%);
    -moz-transform:translate(-50%, -50%);
    -ms-transform:translate(-50%, -50%);
    -o-tranform:translate(-50%, -50%);
    transform:translate(-50%, -50%);
}

5.2 backgournd代图片,backgournd-size

.inner{ background-image: url("...") ; background-size: cover ; height: 100% ; }

说明:

  1. 兼容性:只能是IE9+和现代浏览器

  2. 自适应:背景可以任意自适应于外部容器,或者定高宽。也可以让子容器自适应于内部内容,背景自适应于子容器。

  3. 溢出:支持背景溢出隐藏,但无法溢出显示。好消息是,可以使用background-clip指定背景从哪里消失。

  4. 其他:你甚至可以把.inner的标签省掉,直接把背景放到.outer之上

5.3 flexbox

// TBD
// 很抱歉,由于对flexbox没有深入的了解,我还没有试验出flexbox在子元素溢出时也能保持居中的解决方案……最好的结果是子元素被拉伸(= =)。有人有过实例吗?

说明:

  1. 兼容性:只能是IE9+和现代浏览器

  2. 其他:兼容性是我目前已知的东西……


请输入图片描述

Humphry
16.4k 声望2.8k 粉丝

阿里妈妈招前端,有兴趣请 email 联系~