子容器超过父容器如何垂直居中

leohxj
  • 677

子容器小于父容器的情况,我知道如何垂直居中。

但是当子容器的高度超过父容器的时候,我如何让子容器相对于父容器垂直居中?

PS: 补充一句,子容器宽度是百分比,动态的。父容器的宽度是100%。

效果如图, 让红色部分相对于绿色部分垂直居中:
请输入图片描述

回复
阅读 9.5k
3 个回答
Humphry
  • 16.4k
✓ 已被采纳

更新:出文章啦,还有表格参考哟http://blog.segmentfault.com/humphry/1190000000381042


相信每一个前端都或多或少总结过居中吧。这里是居中的最麻烦最不好处理的一个情形:子元素溢出。

我们在解决这个问题之前,先回顾一下,子元素不溢出父元素的时候,我们常见的CSS居中方案。以及为何在子元素溢出的时候,问题变得棘手。

思路

首先我们确定计算流程:

计算方式

H外 = H内 + 2 * H补 ;

子容器溢不溢出,这个流程都不会变。

我们可以看到,要做到计算,我们必须拿到两个值,一个是H外,一个是H内。

在到了需要JS计算的时刻,这些值都可以通过CSSOM来取得,而使用jQuery具体的做法如@怡红公子所说,就不多说了。

而CSS布局的核心在于,在父容器高宽不固定的时候,如何让浏览器帮我们计算?

我们必须保证浏览器解析到相应的样式时,能够拿到H外和H内。

CSS2.1

子元素负margin方案

这个方案已经滥了。

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

我们不相信浏览器,使用手算,将子元素挪去自身高度的50%。这个方案也可以支持子元素高度溢出的情形。

优点:

  • 父元素可以是height:auto,不需要定死父容器的高度
  • 这个方案全浏览器支持……如果你 a)不介意 b)下决心要加班处理 IE6\7下可能导致的一些布局错乱的话
  • 支持子元素溢出的情形

缺陷:

  • 这个方案需要子容器有一个固定的高,但不能是百分比,子元素若是百分比宽高,margin的百分比是针对父级,因此无效了。
  • 需要人工计算负margin的具体大小
  • 子元素和父元素都需要设置position,这就意味着IE6\7下面的数十个友情附赠的美好bug。
  • 由于position:absolute;,子元素对外层高度、宽度塌陷,不能撑宽父容器了。

父容器 line-height = height 单行方案

.outer {
    height: 300px;
    line-height: 300px;
}

如果内容只有一行文字,很简单。line-height作用于line-box,把line-box撑到和父容器一样高,文字节点默认对齐于line-box的baseline。

内部容器,则需要作为行内元素呈递:

.inner { display: inline-block; }

优点:

  • 没有涉及relative和absolute定位,布局时相对无痛。
  • 全浏览器兼容,当然,考虑到inline-box之间的间隙等问题,需要使用hack来兼容。

缺陷:

  • 父元素必须定高,必须为非百分比单位,否则无法做到line-height等于height
  • 无法覆盖子元素溢出的情形:.inner高度高于line-height时,会直接把line-box撑高,而line-box排版阶段只有从上往下排的唯一一种可能,失去了居中的效果,变成了顶对齐
  • 如果没有文字节点,则需要构建一个100%高的hook,或使用:before伪元素。具体方案见CSS小工具,同时inline-box之间的空格间隙需要被考虑在内,见去除inline-block元素间间距的N种方法
  • line-height是一个可以继承给子元素的值,在这种方案里面,必然会导致line-height被继承,因此在需要排版子元素时,需要复写line-height

这个方案是可以改进,以适应溢出情形的,在中间增加一层足够高(你来定义一个高度,比如10000px)的容器,用负margin方法垂直居中于外层,然后用line-height=height方法让内部居中于中间层。实现比较复杂,也结合了两种方案的优点,和……缺点。个人觉得还不如直接使用负margin方案来得爽快些。

子元素margin:auto方案

.outer{
    position: relative;
    height: 100px ;
    overflow: hidden ;
}
.inner{
    margin-top : auto;
    margin-bottom : auto;
    position : absolute;
    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是可以根据上式算出的,原理类似于水平居中。看到没有,这个包含块高度算式就复现了我们需要的计算过程。

子元素高度溢出父容器时,这个方案依然可行。

优点:

  • 父元素可以是height:auto,不需要定死父容器的高度
  • 无需手算,给出高度,浏览器自己搞定其他的内容
  • 完美支持子元素溢出的情形

缺陷:

  • 这个方案需要子容器有一个固定的高(百分比也可行)
  • 子元素和父元素都需要设置position,这就意味着IE6\7下面的数十个友情附赠的美好bug。
  • 由于position:absolute;,子元素对外层高度、宽度塌陷,不能撑宽父容器了。
  • 这个方案仅仅支持IE8+。IE6和IE7由于对同时定义top、bottom属性的样式解析与 css2.1 不一致,不支持这种定位方式。

这是CSS2.1范畴内适用面最广的垂直居中方式。能够涵盖溢出的情形。

display:table-cell + vertical-align方案

图片垂直居中tabel_cell

优点:

  • 无需手算,子元素不给出高度,浏览器也能自己搞定其他的内容

缺陷:

  • 父元素就算给定高度,设置overflow,也不会导致溢出隐藏;在子元素溢出的时候,父容器甚至都不能保有自身设置的高度,直接会被撑高
  • display:tabel-cell本身让很多属性无效
  • display:tabel-cellIE6和IE7不支持

这个方案不符合楼主的要求,就不多说了。


CSS3

-50% 的 translate方案

居中百分比宽高的元素

.inner{
    position : absolute;
    top: 50%;
    transform: translate(-50%, -50%);
}

这里其实是负margin的改版,仅仅是用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%);
}

background方案

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

如果是一个图片,可以用background-size:coverbackground-size:contain来做到简单的居中。若不需要拉伸,也可以使用background-position-y:center 来做。

为何不把这个方案放在CSS2.1中呢,因为只有在CSS3中,背景才可以相对容器变化大小,比如等于容器高度:

.inner{ background-size: auto 100% ; }

或者等于容器宽度

.inner{ background-size: 100% auto ; }

优点:

  • 支持背景溢出的情形(废话)
  • 图片相对父级的各种情形都可以完美覆盖
  • 你甚至可以把inner的标签省掉,直接把背景放到outer之上

缺点:

  • 只能是图片
  • 只能是背景
  • 只能是IE9+和现代浏览器

flexbox方案

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


其实在这里讨论的居中方案暗含了一个条件:父容器overflow:hidden,父容器本身就有BFC……其实前端排版中的垂直居中还不止于此,父容器可以被撑高是另外一种情形,不过偏离LZ的问题太远,这里不再多说。

如果超过部分上下正好是一样的话,直接用line-height: 子容器高度(单行情况)或者display:inline-table;vertical-algin:middle;(对应其它情况)保证在子容器居中就可以保证相对于父容器居中了吧。另外,如果不兼容过时的浏览器的话,可以试试CSS3的一些东西,做垂直居中还是非常简单方便的:http://zh.learnlayout.com/flexbox.html

如果超过部分上下不一样的话我暂时只能想到用JavaScript计算差值然后给margin的方法呢。(示例是jQuery的语法,语义版,father是红色方框,子容器的内容需要再包括在一个标签内):

var father = $('.father'), child = father.children(), grandfather = father.parent();
var marginTop = grandfather.offset().top - father.offset().top + (grandfather.height() - child.height())/2;
child.css('margin-top', marginTop+'px');

额,之前好像没看清楚题目,谢谢 @Humphry 提醒。如果只是容器要垂直居中的话可以用JavaScript计算两者高度相减除以2,并给与margin-top,或者直接使用CSS3的calc()计算margin-top的值就好了,关于这个你可以看看这个:http://www.qianduan.net/calc-at-at-at-page-intelligent-layout.html

var father = $('.father'), child = father.children();
var marginTop = (father.height() - child.height())/2;
child.css('margin-top', marginTop+'px');
spencer_Xie
  • 90
宣传栏