相关文章推荐
胆小的豆浆  ·  maven ...·  1 年前    · 

最近处理了一个字体图标没有在容器中垂直居中的样式问题。原本以为,只是 css 写的不正确,实际却并没有那么简单。
一番波折后,最终发现,是因为一处小细节,挖出了个大坑。
在处理问题的整个过程中,一方面复习了相关的 css 基础知识;另一方面,对于问题原因的推理方法上,也给了我一些新的启示,故特此记录下来。

同事小 w 请教了我一个样式问题:他写的页面上,一处本该垂直居中的字体图标,变成了与容器顶部对齐,不知道是哪里 css 写的不对,
如图所示(由于事故现场已不存在,此处为模拟还原的场景): html 结构如下:

<div class='wrapper'>
  <div class='inner'>
    <i class="far fa-check-circle"></i>
复制代码

css 如下:

.outer {
  height: 60px;
  line-height: 60px;
  text-align: center;
  color: white;
  background-color: gray;
.inner {
  display: inline-block;
  font-size: 26px;
  line-height: 1;
复制代码

分析

可以看到,html 结构很简单,分为三部分,即外部容器 .outer ,内部容器 .inner ,以及字体图标本身。
外部容器将 height line-height 同设为 60px,是经典的垂直居中的方法,然而,结果却并没有实现居中。
问题出在哪里了呢?
那么就先从垂直居中的原理来分析一下吧。

利用 line-height 垂直居中

我们经常说, “让 height 等于 line-height 可以实现垂直居中” 。其实,这个说法本身,是有不少问题的。
首先,对于一个容器和一个内联元素来说,并不需要同时设置 height line-height ,只要给容器设置 line-height ,就可以“垂直居中”了,如下图:

<div class='wrapper'>
  <span class='text'>中文文本</span>
复制代码
.wrapper {
  margin-bottom: 20px;
  line-height: 100px;
  color: white;
  background-color: gray;
  text-align: center;
复制代码

不需要设 height 很好理解,因为 line-height 也可以将容器高度撑开,此时的 height 的值为 auto ,自动计算成了 line-height 的值。
之所以会有“让 height 等于 line-height ”的说法,是因为最早这种做法是基于高度已经固定的容器,要使得其中的文本垂直居中,就需要设置容器的 line-height 等于高度。(所以也可以直接去掉高度,改为设置 line-height
但这里还有个问题,其实文本并没有真正的“垂直居中”,确切的说,是文本的“内容区域”居中了。如果给文本设置背景色,就可以看出其内容区域: 幸运的是,对于我们常用的字体,字体设计师在设置字体属性(Font Metrics)时,会尽可能的使字体处在内容区域的垂直居中位置,也因此,字体在容器中也是垂直居中的。
然而,这种幸运,对于 display: inline-block 的元素来说,是不存在的。如下图:

<div class='wrapper'>
  <span class='cube'></span>
复制代码
.wrapper {
  margin-bottom: 20px;
  line-height: 100px;
  color: white;
  background-color: gray;
  text-align: center;
.cube {
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: white;
复制代码

设置 vertical-align

对于 display: inline-block 的元素,要使其垂直居中,我们通常会在元素上添加 vertical-align: middle 使其垂直居中,如图:

.wrapper {
  margin-bottom: 20px;
  line-height: 100px;
  color: white;
  background-color: gray;
  text-align: center;
.cube {
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: white;
  vertical-align: middle;
复制代码

这是什么原理呢?
参考 w3c 对于 vertical-align 的定义,当设置为 middle 时:

Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent.

翻译过来,就是说, vertical-align: middle 的元素,会和父元素所用字体的 baseline 高度加上 x-height 的一半对齐,而这个值,其实就是小写字母 x 的中线的高度,如图: 也因此,可以发现,使用这个方法实现的垂直居中并不可靠,它依赖于父元素的字体属性。事实上,上图中,元素的位置也并没有绝对的垂直居中,原因就是父元素使用的字体,其小写 x 并没有在内容区域垂直居中(偏下了)。
由此也可以解释,如果不设置 vertical-align ,即 vertical-align: baseline 时,为什么元素会偏高:
参见 vertical-align: baseline 的定义:

Align the baseline of the box with the baseline of the parent box. If the box does not have a baseline, align the bottom margin edge with the parent's baseline.

所以,元素块的底部,与 baseline 对齐,也就是字母 x 默认所处位置的那条线:

完成了以上的分析,再回头看最开始的问题,发现,作为内容器作为 display: inline-block 元素,没有添加 vertical-align 属性,于是我们加上它试试:

.wrapper {
  height: 60px;
  line-height: 60px;
  text-align: center;
  color: white;
  background-color: gray;
.inner {
  display: inline-block;
  font-size: 26px;
  line-height: 1;
  vertical-align: middle;
复制代码

发现并没有用。难道是因为只有一个字体图标,没有一个参照物的原因?(之前的中文字符不止一个字)
带着这种设想,在字体图标旁,添加一个字符试试,如下: 果然,当添加了一个字符之后,字体图标也可以居中了。
难道,要使图标居中,我一定要写个隐藏字符在图标旁边才行吗?为什么以前用类似方式,没有遇到该问题呢?
于是,我在其他页面尝试单个字符情况下,是否能居中,结果如下: 为什么其他页面,没有这个问题呢?我再次翻开 w3c 官方定义文档,在行高的章节中,找到这段描述:

The minimum height consists of a minimum height above the baseline and a minimum depth below it, exactly as if each line box starts with a zero-width inline box with the element's font and line height properties. We call that imaginary box a "strut." (The name is inspired by TeX.).

以及在 vertical-align 段落中写道:

The following values only have meaning with respect to a parent inline element, or to the strut of a parent block container element.

简而言之,当只有一个字符时,浏览器会在文本前,设置一个 0 宽度的隐藏字符,作为对齐的参考。所以说,并不需要去手写一个字符来对齐。
这下,我凌乱了。。。

DOCTYPE 陷阱

冷静一下后,继续开始分析。发现,不同页面上,html 结构和 css 是完全一致的,但为什么实际样式会不一样呢?
再根据以上的理论分析,发现没有居中的原因,很可能是浏览器没有提供这样一个 strut ,作为参考。而这,并不符合 w3c 定义的规范。
想到这里,突然意识到了什么,在对比一下页面,发现了唯一一处不同点: 是的,页面没有设置 DOCTYPE!
当 DOCTYPE 没有设置时,页面会进入 Quirks Mode,即怪异模式。
怪异模式是为了解决,css 规范出现后,保证遗留页面依然能够正常显示而制定的一种 HTML 文档类型。正是由于页面处于怪异模式,才并没有依照 w3c 规范的定义,使图标垂直居中。
赶紧添加 <!DOCTYPE html> ,这下问题终于解决了。

首先,对于垂直居中问题,利用 line-height 未必能实现,这个需要根据具体情况来分析,而分析的依据,正是 w3c 中的基本定义。由此可见,对于基础概念,不应当只停留在会用的阶段,还是需要完整的阅读一遍,理解透彻;
其次,对于排查问题,除了结合理论依据外,尝试做对比是一个很好的方式。通过对比,一步步排除不相关原因,最后就能挖出问题所在;
最后,在排查的过程中,决不能无凭无据预设条件。因为在这次,纠结了很久的一个原因,正是因为没有考虑到,页面的标准本身存在问题,而把时间花在寻找使用方式上是不是还存在未知错误。直到后来,一步步排除了之后,才锁定到了最终原因。