《 CSS Secrets 》是 @Lea Verou 最新著作,这本书讲解了有关于CSS中一些小秘密。是一本CSSer值得一读的一本书,经过一段时间的阅读,我、@南北和@彦子一起将在W3cplus发布一系列相关的读后感,与大家一起分享。
问题
44年前我们把人送上月球,但在CSS中我们仍然不能很好实现垂直居中—— @James Anderson
让一个元素水平居中对于CSS来说非常简单:如果是一个内联元素,我们可以在他的父元素上设置 text-align:center; ;如果是一个块元素,我们可以使用 margin:auto; 。然而,只要一想到让一个元素垂直居中,让人死的心都有了。
多年来,垂直居中已成为CSS的不朽神话,也是前端专业人士群体中的一个内部笑话。原因是:
- 经常需要使用
- 理论上看上去非常简单
- 过去实战中要实现是极其困难,特别是元素大小固定时
前端开发人员多年来尽所能的来解决这个问题,共中最令人不安的是使用了很多Hack手段。在这一节中,我们一起探索一些实现垂直居中现代技术。 注意,有一些受欢迎的技术,不在这里讨论,主要是因为:
- 表格布局不讨论(表格显示模式),因为它需要一些冗余的HTML标签
- inline-block 方法不包括,因为要使用很多Hack手段
然而,如果你对这方面感兴趣,你可以阅读Chris Coyier写的博文《 Centering in the Unknown 》,里面介绍了这两种技术。
如果没有特殊声明,后面示例用的HTML结构都是在
元素中插入下面的标签:
<main> <h1>Am I centered yet?</h1> <p>Center me, please!</p></main>
登入後複製
为了得到下图所示效果,我们也应用一些基本的CSS样式,比如 background 、 padding 等等:
绝对定位解决方案
最早实现垂直居中的技术是元素需要一个固定的宽度和高度:
main { position: absolute; top: 50%; left: 50%; margin-top: -3em; /* 6/2 = 3 */ margin-left: -9em; /* 18/2 = 9 */ width: 18em; height: 6em;}
登入後複製
从本质上讲,它将元素的左上角点移到视窗中心点,然后使用负的 margin (使用 margin-top 和 margin-left ),而且 margin 的值是元素宽度和高度的一半,使元素的中心点与视窗的中心点重合。如果使用 calc() 可以减少两个样式:
main { position: absolute; top: calc(50% - 3em); left: calc(50% - 9em); width: 18em; height: 6em;}
登入後複製
显然,这种方法最大的问题是,元素需要一个固定的尺寸,而需要垂直居中元素的尺寸常常是需要由它的内容来决定。如果我们有办法使用百分比来控制元素尺寸,我们的问题就解决了。不幸的是,对于大多数CSS属性(包括 margin )百分比的值是相对于其父元素的宽度来决定。
在CSS中常常可以看到很多解决方案是不可思议。在这个例中,就可以使用CSS3的 transform 。可以在 transform 中的 translate() 使用百分比,让元素移动是相对于自身的宽度和高度,这种方案正是我们需要的。
main { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);}
登入後複製
当然,没有哪门技术是完美的,在实际使用中要注意一下以下几个事项:
- 绝对定位通常不是一个很好的选择,因为它对整体的布局影响相当的大。
- 如果垂直居中元素的内容比视窗高度更高的话,它的顶部会被裁剪掉,如下图所示。但是这个问题可以解决,只不过需要使用一些Hack手段。
- 在一些浏览器中,可能会导致元素出现略微的模糊,那是因为元素有可能被放置在半个像素位置上。我们可以通过 transform-style:preserve-3d 来解决,尽管这是一个Hack手段,不能保证它不会过时。
视窗单位的解决方案
如果想避免使用绝对定位,我们仍然可以使用 translate() 方法,其值刚好是元宽度和高度的一半。然而,我们如何不使用 top 和 left 将元素从 top 和 left 移动 50% 的偏移量呢?
首先想到的是给 margin 属性一个百分数,像这样:
main { width: 18em; padding: 1em 1.5em; margin: 50% auto 0; transform: translateY(-50%);}
登入後複製
然而,正如你所看到的效果,如下图所示:
这产生了非常奇怪的效果。主要原因是 margin 的百分比计算是相对于父容器的 width 来计算。是这样的,甚至包括 margin-top 和 margin-bottom 。
值得庆幸的是,如果我们想让元素在视窗中居中,还是有希望的。 CSS Values and Units Level 3 定义了一种新的单位,称为相对视窗( viewport-relative )长度单位。
- vw 是相对于视窗的宽度。与你预期刚好相反, 1vw 相当于视窗宽度的 1% ,而不是 100%
- 与 vw 相似的是, 1vh 相当于视窗高度的 1%
- 如果视窗的宽度小于高度, 1vmin 等于 1vw ,反之,如果视窗宽度大于高度, 1vmin 等于 1vh
- 如果视窗的宽度大于高度, 1vmax 等于 1vw ,反之,如果视窗宽度小于高度, 1vmax 等于 1vh
在这个示例中,我们需要给 margin 的值设置 vh 单位:
main { width: 18em; padding: 1em 1.5em; margin: 50vh auto 0; transform: translateY(-50%);}
登入後複製
正如你看到效果很完美。
当然,这种方法有用性是有极限的,因为它只适用于元素在视窗中垂直居中。
请注意,还可以使用相对视窗单位来创建全屏效果,而且不需要使用任何脚本。更多细节可以阅读 Andrew Ckor 写的《 Make full screen sections with 1 line of CSS 》博文。
Flexbox的解决方案
这无疑是最好的解决方案,因 Flexbox 的出现就是为了解决这样的问题。其他解决方案仍然可用,唯一原因是他们能更好的在浏览器上呈现,不过Flexbox在现代浏览器也得到更好的好支持。
只需要两个样式,在需要垂直居中的父元素上设置 display:flex (这个示例中就是
)和在垂直居中的元素上设置 margin:auto (这个示例中就是
): body { display: flex; min-height: 100vh; margin: 0; }main { margin: auto;}
登入後複製
注意,当使用Flexbox和 margin:auto 时,元素不仅水平居中,而且也会垂直居中。也注意,我们甚至没有设置宽度(如果我们想要也可以设置),其实指定的宽度相当于 max-content 。
如果浏览器不支持Flexbox,那么结果看起来就会像下图(如果我们给元素设置了宽度):
即使不是垂直居中,还是可以接受的。
Flexbox的另一个优点是,可以让匿名容器垂直居中。例如,我们将结构换成:
<main>Center me, please!</main>
登入後複製
我们可以通过 align-items 和 justify-content 属性使设置固定尺寸的 容器里面的文本居中。如下图所示:
我们可以在
和需要居中的元素 使用相同的属性,同时使用 margin:auto 做为备用,以于优雅降级。 main { display: flex; align-items: center; justify-content: center; width: 18em; height: 10em;}
登入後複製
新特性:对齐所有东西
CSS Box Alignment Level 3 已经在计划,在未来我们甚至不需要使用不同的布局模式就能非常容易的实现垂直居中,我们只需要像下面这样做:
不管元素上使用其他样式,这个将来都能运行。这听起来令人难以置信,但将来在浏览器中是可以渲染的。