上一篇中提到自己 debug 时遇到一个 css 知识盲点:行高和字体的关系。随后查了相关资料,在这里梳理下这个知识,最后解释下产生这个现象的原因。
现象
一般我们要让文字在容器中产生垂直居中的效果,会把容器高度和行高设置成一样,来达到这个效果(代码,效果图如下)。
Test
但是当容器的字体大小和文字的字体大小不一致的时候,字体就无法居中了(代码,效果图如下)。
Test
原理概述:行内元素如何排版
在解释上述现象之前,我们先来看下行内元素(inline-element)在页面上是如何排版的。我们先假设页面上没有任何我们添加的 css,每个行内元素会占据一定的矩形空间,我们称之为行内框(inline-box),如下图和代码:
span em I am very very very very very very strong
span
em
strong
都是行内元素,背景颜色标出了它们行内框所占的空间(这里只是为了图示方便,用颜色标示了行内框占据的大致空间,因为 css 中并没有给行内元素的行内框设置颜色的属性)。而如果没有用行内元素包裹的文字呢?浏览器会给文字生成一个匿名的行内框,如下图ih-pic3.png 中用绿色框框出来的所示。
anonymous span em
上面两个例子字体都是统一大小的,如果每个行内元素字体不一样大小呢?每个行内框会根据各自的 vertical-algin
在垂直方向上对齐,我们先看下示例(示例中每个行内元素的 vertical-algin
都设置成一样,但是每个元素的 vertical-algin
也可以各自不同,这里我就省略了各自不同的例子):
anonymous span em I am very very very very very very strong
如果一行中的行内元素字体大小和 verticial-align
各不相同,那么整个一行占据空间是如何算的呢?这一行的高度最高处由垂直方向上行内框最高的元素决定,最低处由垂直方向上行内框最低的元素决定(代码,图如下):
anonymous span em I am very very strong
图ih-pic9.png 中黄色框表示的就是整行占据的空间,我们称之为行框(line-box)。再给一个带上下标的示例看看是如何计算行框占据的空间(还是黄色框占据的空间表示行框):
anonymous spansup emsub I am very very strong
所以行内元素字体大小决定了字的大小,每个行内元素占据的空间又由行内框(inline-box)决定,最后一整行如果只有行内元素,则所占据的空间就是行框(line-box)决定的,高度亦是行框的高度。如果文字很长折成了多行,那么每行都是按照以上规则生成多个行框,并在垂直方向上以容器内部边界的左上角开始向下排列:
接下来我们就要看下 line-height 是怎么影响元素的了。刚刚我们提到一个行内元素占据的空间叫行内框,那么行内框的高度是如何决定的呢?就是 css 属性行高(line-height
)决定,浏览器通常全局的默认行高是 normal,约等于字体的1.2倍。譬如一个行内元素的字体是 12px,那么行高就是 12 x 1.2 = 14.4(px)。而行高和字体间的距离差距我们叫做 leading(中文可以叫做行间距,不过网页上的行间距和传统印刷业的行间距代表的地方不同,这里就不描述其历史和不一样的地方了)。浏览器会把 leading 除以二,平均放到字体的上面和下面,这两块平均空间的距离叫做 half-leading,如下图(图中为了演示明显,字体大小设为 16px,行高设为 32px):
如果 line-height 小于字体大小,那么行内框的高度就会向字体的中心线收缩,字体将会溢出行高,如下图(图中为了演示明显,字体大小设为 32px,行高设为 20px):
行内框的高度会影响行框的高度,我们假设极端情况每行只有一个行内元素,那么每行行框(line-box)的高度就是这个元素行内框(inline-box)的高度,让我们看看多行情况下行高(line-height)大于字体和小于字体产生的效果,溢出行高的字体将在多行垂直方向叠加:
最后我们说下如果手工设置了元素的 line-height,将如何影响元素:
- 行内元素设置 line-height 等于指定了行内框的高度。
- 没有行内元素包裹的文字所生成的匿名行内框的高度是继承自它的父容器。
- 块级元素包裹了一系列行内元素,这些行内元素自己没有设置 line-height,那么给块级元素设置 line-height 等于给被包裹的行内元素指定了最小行内框的高度。
现象产生原因分析
一般情况:
Test
div 设置的 line-height:32px 给 span 限定了行内框的最小高度,而 span 的默认行高和字体都没有超过 32px,所以 span 的行内框高度就是 32px,又因为 half-leading 平均分配的原理,那么字体就会在行内框中居中,如果一行字行内框高度相同,那么整行文字的行框高度就是 32px,最后 div 的高度又设置了 32px,所以最后文字看起来是垂直居中的。(其实如果以此例来说 div 不设置高度也没有问题,因为 div 高度就是按内部元素高度而撑开的)
异常情况:
Test
虽然看上去 div 只有一个子元素 span,但是从规范上我得知,这种情况下有一个宽度无限小的匿名行内框存在。如果在 div 上设置了字体 42px,那么这个匿名行内框的字体就变成了 42px,且 div 的 line-height 只能指定匿名行内框的最小高度,当匿名行内框的字体大于 32px 时,这个匿名行内框的高度就变大超过了 32px,最后导致整行的行框高度超过 32px。而 span 的行内框和字体虽然都没有超过 32px ,但是它的 baseline 需要在水平方下沉和这个匿名行内框对齐,但是 div 的高度限制成了 32px,最后看上去 span 就不在垂直居中的方向上了。
参考资料
- https://www.slideshare.net/maxdesign/line-height
- https://www.w3.org/TR/REC-CSS2/visuren.html#inline-formatting
- http://www.zhangxinxu.com/wordpress/2009/11/css%E8%A1%8C%E9%AB%98line-height%E7%9A%84%E4%B8%80%E4%BA%9B%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E5%8F%8A%E5%BA%94%E7%94%A8/