原文出处:http://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
- 无授权,基本上是翻译原文,加上自己的理解和注释,当做一个笔记。
作者说:这两个属性也许没有看上去那么简单,甚至是最难的之一。原因在于:内联格式化上下文(inline formating context)。
因为我们对这两个属性知道的太少了,比如line-height:normal,设置行高为正常,但是什么是正常呢?也许是1,也许是1.2,在字体这么多设置中,它们的行为总是一样吗?而且它们有什么影响吗?
之后举了一个font-size都是一样的三个一样标签的内联元素,字体不同,发现不一样高,而且有一个高出了64px。
font是根源所在:
1.一个字体(font)定义它的em-square(UPM ,unit per em),类似于容器,每个字符都会被绘制。这个方形的容器使用相对单位,一般设置为1000,但是它也可以是1024或者任何其他。
2.根据相对单位,可以设置上升下降(ascender,descender,capital height,x-height等等)。一些值也有可能泄露在容器外面。
3.在浏览器中,相对单位被缩放以适合期望的字体大小。
这里作者用了一个软件检测字体的详细指标。(https://fontforge.github.io/en-US/)
上升+下降的值=字体em-square大小。
Mac OS上相对应的是HHead Ascent / Descent值,在Windows上使用Win Ascent / Descent值,值也许会有不同。
比如一个ascent(上升)是1100,descent(下降)是540,em-square是1640,设置时的高度就是164px,这个计算的高度定义了一个元素的内容区域。内容区域视为属性应用的区域。
- 1ex是小写字母,也就是对应的x-height,所以它是一个会变的数值,而不是像em基于font-size,而不是计算高度。
可以看到,它可以根据其宽度由许多线组成。每行由一个或多个内联元素(HTML标签或文本内容的匿名内联元素)组成,称为行框(line-box),一个line-box是基于其子女的高度。
因此,浏览器计算每个内嵌元素的高度,并且因此定下line-box的高度(从其子的最高点到其子的最低点)。因此,行框总是足够高以包含其所有子项(默认情况下)。
其实知道每个line-box的高度,就知道一个元素的高度。
但是实际是看不到line-box的,也不能用CSS控制它,设置个背景也没有多大用处。
<b>line-height:to the problems and beyond</b>
一个line-box的高度由孩子的height计算,没有说它孩子content-area的高度。这产生了很大的区别。
即使它可能听起来很奇怪,一个内联元素有两个不同的高度:内容区域高度(content-area)和虚拟区域(virtual-area )高度(作者发明了术语虚拟区域O__O "…)。
1.在内容区高度(content-area)由字体规格(font metrics)定义(如前所示)
2.在虚拟区域的高度是line-height,它的高度用于计算line-box的高度(it is the height used to compute the line-box’s height)
计算出的虚拟区域和内容区域之间的高度差称为leading。一半leading被添加在内容区域的顶部,另一半被添加在底部。因此,内容区域总是在虚拟区域的中间。
基于其计算值,line-height(虚拟区域)可以等于,高于或小于内容区域。在较小的虚拟区域的情况下,leading是负的,并且line-box在视觉上小于其孩子。
还有其他种类的内联元素:
取代内联元素(<图片>,<input>,<svg>等)
inline-block和所有inline-*元素
参与特定格式化上下文的内联元素(例如,在flexbox元素中,所有flex项目都被块化)
对于这些特定的内联元素,高度的计算基于他们的height,margin和border属性。如果height是auto,则line-height使用并且内容区域严格等于line-height。
之后是讨论了两种字体的line-height:1,发现内容区域不一样大,
<b>很明显,设置line-height: 1是一个坏的做法。</b>
无单位的值是font-size相对的,而不是内容区域相对的,并且处理小于内容区域的虚拟区域是我们的许多问题的起源。
并且作者发现在1117中字体中1059字体,大约95%,有一个line-height计算大于1.他们的计算从0.618到3.378。
<b>vertical-align:one property to rule them all</b>
奇怪的是,默认的基线对齐可能会导致更高的(!)行框,一个行框的高度是从它的孩子的最高点到它的孩子的最低点计算的。
继承的font-family可能不是想象中的大小,因为每个标签的基准(baseline)很可能是不同的,所描述的line-box高于预期。因为浏览器计算每个line-box都是以0宽度开始的,称为strut。(This happens because browsers do their computation as if each line-box starts with a zero-width character , that the spec called a strut.)
规范是这么写middle的 “aligns the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent”,基线比不同,x-height比也不同,使用align也不可靠。middle不是真的在中间。
vertical-align: top / bottom align to the top or the bottom of the line-box
vertical-align: text-top / text-bottom align to the top or the bottom of the content-area
注意,在所有情况下,它对齐虚拟区域,因此是不可见的高度。看看这个使用vertical-align: topline-height的例子。看不见可能产生奇怪,但不令人惊讶的结果。
最后,vertical-align还接受提高或降低关于基线的数值。最后一个选项可能派上用场。
<b>CSS is awesome</b>
我们已经讨论过如何让line-height和vertical-align一起工作,但现在的问题是:字体指标可以用CSS控制吗?
简答:不。
字体指标是不变的,所以我们应该能够做一些事情。
例如,如果我们想要一个使用 Catamaran的文本,其中captical-height正好是100px高?
看起来可行:让我们做一些数学。
首先,我们将所有字体指标设置为CSS自定义属性,然后计算font-size获得100px的captical-height。
p {
/* font metrics */
--font: Catamaran;
--capitalHeight: 0.68;
--descender: 0.54;
--ascender: 1.1;
--linegap: 0;
/* desired font-size for capital height */
--fontSize: 100;
/* apply font-family */
font-family: var(--font);
/* compute font-size to get capital height equal desired font-size */
--computedFontSize: (var(--fontSize) / var(--capitalHeight));
font-size: calc(var(--computedFontSize) * 1px);
}
很简单,不是吗?但是,如果我们想让文本在视觉上位于中间,以便剩余的空间平均分布在“B”字母的顶部和底部?为了实现这一点,我们必须vertical-align根据上升/下降比率来计算。
首先,计算和内容区域的高度:line-height: normal
p {
…
--lineheightNormal: (var(--ascender) + var(--descender) + var(--linegap));
--contentArea: (var(--lineheightNormal) * var(--computedFontSize));
}
然后需要:
the distance from the bottom of the capital letter to the bottom edge
the distance from the top of the capital letter to the top edge
像这样:
p {
…
--distanceBottom: (var(--descender));
--distanceTop: (var(--ascender) - var(--capitalHeight));
}
我们现在可以计算vertical-align,这是乘以计算的距离之间的差异font-size( which is the difference between the distances multiplied by the computed font-size这句啥意思??)。(我们必须将此值应用于内联子元素)
p {
…
--valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize));
}
span {
vertical-align: calc(var(--valign) * -1px);
}
最后,我们设置所需的line-height并计算它,同时保持垂直对齐:
p {
…
/* desired line-height */
--lineheight: 3;
line-height: calc(((var(--lineheight) * var(--fontSize)) - var(--valign)) * 1px);
}
结果就是特别完美的居中。图就是传不上来……
注意,此测试仅用于演示目的。
你不能依赖这个。
很多原因:
除非字体指标是不变的,浏览器中的计算不是。
如果字体未加载,则后备字体可能具有不同的字体度量,并且处理多个值将很快变得非常难以管理。
<b>Takeaways</b>
我们学到了什么:
内联格式化上下文真的很难理解
所有内联元素都有2个高度:
在内容区域content-area(根据字体规格)
在虚拟区域virtical-area(line-height)
这2个高度中没有一个可以可视化,毫无疑问。
(如果你是一个devtools开发人员,想要工作,这可能是真棒)line-height: normal 是基于字体度量
line-height: n可以创建小于内容区域的虚拟区域
vertical-align 不是很可靠
一个line-box的高度是根据它的孩子们的line-height和vertical-align
属性来计算我们不能很容易地获取/设置CSS的字体指标
有一个相关的未来规范来帮助垂直对齐:线网格模块
但我还是喜欢CSS :)
后记:没想到花了好几个小时,很佩服原作者这种默默的奉献,他一定花了比我更多的时间去写,去做图让读者更好理解。