本文在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:visible
或display:table-cell
等等。 - 子容器溢出时,父容器出现滚动条。父容器
overflow:scroll
或overflow:auto
很显然,子容器溢出时,被父容器截断的情形无法和父容器自适应于子容器共存。
兼容性
- IE6、IE7:老而不死的浏览器,浏览器尚未统一、IE一家独大之时的遗毒,一大堆bug等着你。
- IE8:IE8起全面支持CSS2.1,剩下一些稍微少坑爹那么一点点的bug。
- IE9。尴尬的产物,微软第一次搭上CSS3的末班车。
- IE10+与其他现代浏览器(终于可以和其他浏览器并列了……IE10泪目)。
4、3、2、1的兼容难度是一步步变难,兼容到4、3、2、1所对应的代码量/工作量是100%、102%、120%、300%的关系。
在这里仅仅考量IE6/7,IE8的无bug实现的兼容性。IE9+的兼容性,对于文中提到的所有方案都是可行的。
其他
- 是否需要手动计算/需要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 ; }
说明:
- 需求:父子都需要自适应于内部的解决方案里,是把留白的高度固定,这种需求其实很少见。
可缺省:
height:auto
可以省去,这是默认值。
对于块级元素,默认是height:auto;width:auto;
width的auto值是自适应于这个元素的包含块的宽度,而height自适应于这个元素的content-box的高度自适应:父子容器均可以自适应于内部;也可以父容器自适应,子容器定高。
因为height:auto
时,计算高度值的依赖方向是从外往内。自适应:为何在这俩方案里不容许出现父容器或子容器自适应于外层呢?
因为对于height来说,它的百分比值是乘以包含块的height,但padding-top
/padding-bottom
/margin-top
/margin-bottom
的百分比值是乘以包含块的width,而非我们希望的height。溢出:
* 父容器上下等padding时,父容器定高时,子容器溢出padding-edge的部分会被截断。
* 子容器上下等margin时,父容器自适应于内部,父容器只会被撑高,永远无法被截断。
* 其他情形,截断都没有什么意义
1.3. background代图片background-position
在子容器是<img>
标签时,可以直接使用background来替代它。也可以算得上一种方案。
.inner { background-image: url("...");
background-position: 50% 50%;/*注,火狐不支持background-position-y的属性设置*/ }
说明:
自适应:无法自适应于外部容器,CSS2.1阶段,background无法相对于容器伸缩。
溢出:背景溢出的情形,直接会被截断。
其他:你甚至可以把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/
说明:
兼容性:没有涉及relative和absolute定位,布局时相对无痛。
兼容性:这个方案是IE6\7不支持的,原因不详(只知道
inline-block
和vertical-align
的标准诞生于IE8之后),也没有时间研究。2014年了,有点追求好吗。自适应:子容器可以轻松做到自适应于内部、外部或者定高,但在这个方案里,父容器必须定高,因为
line-height
的百分比单位是相对font-size
来说的。溢出:子容器溢出时会变成顶对齐,这是因为,line-box 的高度跟内部最高的 inline-box 相等,因而line-box 可以被撑高,从上往下排一个个排列下来,从而失去了居中的效果。换句话说,子元素溢出时,父容器可以自适应于内部。
其他:
line-height
和font-size
是一个可以继承的属性,在这种方案里面,必然会导致line-height
、font-size
被继承,因此在需要排版子容器时,需要复写line-height
、font-size
。
2.2 子容器所在的line-box的line-height=父容器的height
对上面的方法不兼容IE6\7且不能做到父容器自适应的方面,可以这样改进:
- 把子容器当成行内元素呈递
- 构建一个行内级别的钩子元素,紧挨着子容器,以使用
:before
/:after
伪元素自制一个文本节点、或<span>
或任意一个inline级别的标签、或者一个1*1的图片。 - 让钩子元素撑满容器高度,这意味着它需要成为
inline-box
元素,然后设置height:100%
即可。 - 现在,子容器所在的 line-box 的 line-height = 父容器的 height。
- 最后,给子容器和钩子元素设置
vertical-align:middle
,让它们的中心线对齐于于父容器的middle-line,就能做到垂直居中。
http://jsfiddle.net/humphry/86dsC/21/
现在这个布局可以自动生成,详见@林小志的css小工具:图片垂直居中span。
(如果需要水平居中,那么这个额外的节点,需要移去自身所占的距离,一般使用margin-left:-1px
或margin-right:-1px
,取决于钩子在子容器的哪一边。)
说明:
兼容性:没有涉及relative和absolute定位,布局时相对无痛。
兼容性:inline-box标签之间的任意数量的空白符:ASCII 空格 (
 
)、ASCII 制表符 (	
)、ASCII 换页符 (
)、零宽度空格 (​
) 会被浏览器解析成一个空格,造成间隙。需要通过改变HTML结构/使用负margin等来去掉这个间隙。兼容性:这个方案有一定hack量,去掉inline-box的空格间隙是一部分,
display:inline-block
的IE6\7 hack是另一部分。自适应:父容器可以自适应于外部了,因为这里不需要在任何地方知道父容器的高度。
溢出:子容器溢出时会变成顶对齐,原因同上。
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
说明:
兼容性:IE6/7不兼容
display:table-cell
。自适应:子容器无法自适应于父容器,高度无法使用百分比单位,因为根据渲染规则,
display:table-cell
的元素的包含块是它父级的display:table
的元素。溢出:父元素就算给定高度,设置overflow,也不会导致溢出隐藏;在子元素溢出的时候,父容器不能保有自身设置的高度,直接会被撑高。
其他:display:tabel-cell本身让很多属性无效。
3.2. 子容器被table、tr、td包裹
为了可以让子容器有百分比高度,我们可以直接构建一个表结构出来:
http://jsfiddle.net/humphry/Ns4RK/8/
说明:
兼容性:这是一个全兼容的方案。
自适应:现在“父级”的自适应要求都可以得到满足,只不过这里的“父级”指的是包在最外层的
table
。溢出:父元素就算给定高度,设置overflow,也不会导致溢出隐藏。
其他:文档结构变复杂了,语义被抛弃了。
其他:display:tabel-cell本身让很多属性无效。
其他:可以把这个方案里的
<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%。
说明:
兼容性:子元素和父元素都需要设置
position
,这就意味着IE6\7下面的数十个友情附赠的美好bug。自适应:子元素必须定高,不定高算不出来负margin。不可以是百分比高度单位,因为
margin-top
的百分比,是相对其包含块的宽度而言的。自适应:父容器可以自适应于内部,只不过不是这个子元素,而是父容器内部其他的元素。子元素对外层高度、宽度塌陷,不能撑宽/撑高父容器了。
溢出:这个方案也可以支持子元素高度溢出的情形。
其他:需要手动计算子元素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-top
和margin-bottom
是可以根据上式算出的,原理类似于水平居中。
说明:
兼容性:这个方案仅仅支持IE8+。IE6和IE7由于对同时定义
top
、bottom
属性的样式解析与 css2.1 不一致,不支持这种定位方式。自适应:父容器可以自适应于内部,只不过不是这个子元素,而是父容器内部其他的元素。原因同上。
自适应:这个方案需要子容器有一个固定的高,或百分比自适应于外部。它的高度不能是
height:auto
,因为这样会使得上面的算式里auto出现在三个地方,浏览器无法计算出相应margin
值。溢出:这个方案也可以支持子元素高度溢出的情形。
其他:完全不用算,耶!
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
的百分比偏移量是容器本身的。
说明:
兼容性:ie9+(但,从好处来讲,其实这个兼容性已经完全不需要考虑IE6~8的相对/绝对布局bug了)
自适应:子元素可以不指定高度,也可以相对父级高宽做百分比设置,也包括定宽,非常灵活
自适应:父容器可以自适应于内部,只不过不是这个子元素,而是父容器内部其他的元素。原因同上。
溢出:支持子元素溢出隐藏,或者溢出显示。
其他:你会让代码陷入一个前缀的海洋……
.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% ; }
说明:
兼容性:只能是IE9+和现代浏览器
自适应:背景可以任意自适应于外部容器,或者定高宽。也可以让子容器自适应于内部内容,背景自适应于子容器。
溢出:支持背景溢出隐藏,但无法溢出显示。好消息是,可以使用
background-clip
指定背景从哪里消失。其他:你甚至可以把
.inner
的标签省掉,直接把背景放到.outer
之上
5.3 flexbox
// TBD
// 很抱歉,由于对flexbox没有深入的了解,我还没有试验出flexbox在子元素溢出时也能保持居中的解决方案……最好的结果是子元素被拉伸(= =)。有人有过实例吗?
说明:
兼容性:只能是IE9+和现代浏览器
其他:兼容性是我目前已知的东西……
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。