原文地址:Deep dive CSS: font metrics, line-height and vertical-align
注:翻译中对内容进行了不少简化,力求不陷入繁缛的细节。
文章目录
- 你真的了解它们吗
- 首先聊聊font-size
- line-box 和 inline-element
- line-height相关的问题
- vertical-align一个属性将它们串起来
- CSS真奇妙
- 知识点总结
你真的了解它们吗
很多人以为自己知道 line-height
和 vertical-align
是怎么工作的,以及怎么使用它们。但实际上,它们非常复杂,并且是在CSS特性 inline formating context 中扮演主要的角色。
测试你的理解情况,可以尝试回答下面几个问题:
问题1:line-height 可以是具体的单位值,也可以是无单位数值,但它的默认值是 normal,什么是 normal?通常我们把它当作1或1.2,又或者是1到1.2之间的数值?CSS的规范中都没有清楚地说明这一点。
问题2:我们知道line-height使用无单位数值的是相对 font-size而言,但问题是font-size:100px 对于不同的字体而言有着截然不同的表现,所以line-height的效果是相同的还是不同的呢?
问题3:vertical-align 对 line-height 有什么影响呢?
首先聊聊font-size
下面的简单HTML代码中,三个 <p>
标签包含了对应的 <span>
,区别在于它们有不同的 font-family
:
<p>
<span class="a">Ba</span>
<span class="b">Ba</span>
<span class="c">Ba</span>
</p>
p { font-size: 100px }
.a { font-family: Helvetica }
.b { font-family: Gruppo }
.c { font-family: Catamaran }
相同的 font-size
作用在不同的 font-family
上导致了不同高度,原因是:
- 每个字体都有它的em-square(默认字体容器高度,字体就在这个容器内被绘制),通常它的基准单位被设置为1000单位,但也有字体是1024、2048单位,甚至任何单位
- 正是基于这个em-square的单位,字体的ascend,descend,capital height,x-height等被确定
用 FontForge 打开 Catamaran 字体,可以看到它的一些数值(结合下面两张图来看):
- em size(基准单位):1000
- ascent(上升部分):1100
- descent(下沉部分): 540
- capital height(大写高度): 680
- x height(小写高度): 485
总结:
Catamaran字体在1000单位的em-square 中使用了1640单位的高度,设置 font-size:100px
最终得到了 164px
的高度,我们称这个高度为 content-area
,你可以将它等同于 background
起作用的高度。
line-box 和 inline-element
在下面的例子中, <p>
元素受限于它的宽度,视觉上分了三行展示,每一行称作 inline-box。每一行都由匿名 或 带HTML标签的inner element组成,inline-box 的高度由它所包含的 inner element 决定。
line-box的高度肯定能容纳它的所有子元素,因此如果你知道了一个HTML元素的所有line-box的高度,那么你就知道了这个元素的高度(相加即可)。
下图中可以看到第二个line-box的高度大于另外两个,因为 Catamaran 字体的 content-area 高于其他的字体。
<p>
Good design will be better.
<span class="a">Ba</span>
<span class="b">Ba</span>
<span class="c">Ba</span>
We get to make a consequence.
</p>
不过实际应用中,如果有多个line-box你是无法看到单个line-box的高度的。
line-height相关的问题
一个 inline element 有两个高度
- content area,字体内容的高度(计算公式为:ascend+descend)
- virtual area,用于计算line-box的高度,等于 line-height(作者发明了这个称呼)
virtual area 和 content area 高度的差值,分别对半被添加到了顶部和地步的 leading 中。依据 line-height的具体知道,这个差值可能为0、正数、负数。负数意味着virtual arae的高度低于content area。
还有一些类型的inline elements,他们的高度会由 height 、marign、border属性决定。如果 height 设置为 auto,那么(似乎默认就设置了,因此此处不强调它) content-area 的高度严格和line-height相等。
- replaced inline elements:
<img>
<input>
<svg>
等 -
inline-block
和inline-*
元素 - 在某些特殊的formating content 中(比如 flexbox中的元素)的inline元素
注:你可以尝试将 <p>
包含的 <span>
设置 display:inline-block,可以看到 content-area(背景颜色区域)变得和virtual area一样了。
所以回到开篇问题:line-height 的 normal 的值是多少?
我们以另外一个字体 Arial 为例,它的 em-square取的基准单位是 2048,ascender的高度是1854单位,descender的高度是434单位,行间距是67单位,计算得到font-size:100px时,content-area的高度是:
(1854+434) / 2048 * 100=112px(小数点后四舍五入),line-height:normal的高度是:(1854+434+67)/2048*100=115px。因此 line-height:normal 的高度通常是由字体的设计者决定的。
所以显而易见的是:设置 line-height:1
是一种糟糕的实践,因为我们它相对的是font-size(即em-square),而不是 content-area,结果就是 virtual area 比 content area小。
我(注:原文作者)统计了电脑上安装的1117中字体,发现95%的字体 line-height的值都大于1,计算得到的line-heigt的值在 0.618到3.378之间。
一些line-box计算的知识点
- inline元素,padding和border会导致背景区域变大,但是content-area高度不变,可知content-area的高度并不总和你看到的一致;margin-top和margin-bottom没有效果。
- 对于replaced inline elements设置 inline-block后,padding margin border都会增加高度:包括content-area和line-box的高度。
vertial-align一个属性将它们串起来
虽然到目前位置我们还没提起过 vertical-align,但实际上它可能是 inline formating context 的最重要的概念。
vertical-align 的默认值是 baseline
,如果你还记得前面条的 ascender 和 descender的话,它们的value决定了baseline的位置,以及上下leading的比率(很少是50/50)。
<p>
<span>Ba</span>
<span>Ba</span>
</p>
p {
font-family: Catamaran;
font-size: 100px;
line-height: 200px;
}
一个 <p>
标签包含两个相邻的 <span>
元素,它们都继承相同的 font-family
, font-size
和 line-height
。两个 <span>
元素的baseline对齐并且line-box的高度和line-height一样。
如果我们把第二个元素的 font-size
设置得更小一些会怎么样?
span:last-child {
font-size: 50px;
}
可以看到,默认的baseline对齐导致了更高的line-box:line-box的高度计算方式是由子元素的最高点到最低点
让我们看看另外一个例子,一个 <p>
标签设置了 line-height: 200px
,包含一个 <span>
继承了父级的 line-height
:
<p>
<span>Ba</span>
</p>
p {
line-height: 200px;
}
span {
font-family: Catamaran;
font-size: 100px;
}
line-box的高度会是多少呢?我们期望它是200px,事实上 <p>
有它自己的 font-famliy
,结果就是 <p>
的baseline和 <span>
的baseline不同,更高的baseline导致了更高的context-area的高度。在规范中这个规则叫strut:0宽度的字符也会参与line-box的计算当中。
直接使用 vertical-align: middle
能解决这个问题吗?如规范中所说, middle
对齐的是:垂直中点加上x-height高度的一半。考虑到上下ratio并不相同,设置为 middle 也不可靠,在大多数情况下,middle 并不表示的绝对中间位置。
顺带提一下,还有4个值可能会有用:
-
vertical-align: top / bottom
对齐line-box的头部和底部 -
vertical-align: text-top / text-bottom
对齐content-area的头部或底部
当仍旧要小心的是,多数情况下对齐的参照标准是 virtual area,看看同样使用了 vertical-align: top
的例子:line-height导致了奇怪但并不令人惊讶的结果
最后, vertical-align
可以忽略baeline的位置接受数字类型的值,它可能会派上用场。
CSS真奇妙
最后原文作者演示了如何设置一个字体的各个部位的高度,可以做到让不同字体在同一个 line-box齐整的对仗,但在实际中有若干理由并不推荐你这么做。
知识点总结
- inline formatting context 是一个实在难以理解的概念
- 所有的inline elements有两个高度:context area 和 virtual area,并且它们都无法被视觉查看到
-
line-height: normal
是基于每种字体的设计而定的 -
vertical-align
是不可靠的 - 一个 line-box 高度计算是基于所有子元素的
line-height
和vertical-align
- 在CSS中查询或设置字体的度量并不容易