处理垂直居中与隐藏属性Font Metrics

最近在优化公司内部UI组件时,遇到了一个问题:
我们的字体图标在跟文字放在一起时,如果不写专门的样式,看起来“没有对齐”,如图:

那要怎么做才能使图标跟文字垂直居中呢?
可能,最先想到的是在图标上添加 vertical-align: middle
呃,好像还是没有居中。根据经验,在文字上也加上 vertical-align: middle
诶,这下好像基本对齐了。
那么问题来了,是不是每次使用图标加文本都需要这样对齐呢?
如果我的图标是出现在文本中间的,那又怎么办呢?
这个图标跟文字真的绝对对齐了么?这样做是不是真的合理呢?
要解答这些问题,那得先从IFC说起……

IFC

IFC是什么?
在理解浮动以及清理浮动的原理时,相信很多人都接触过BFC(Block Formatting Contexts),定义了在“普通流”(Normal Flow)中,块级盒子(Block Box)组成上下文中的表现特性。
而IFC,即Inline Formatting Context,顾名思义,定义了“行内盒子”(Inline Box)组成上下文中的表现特性,完整定义可见CSS标准文档
根据IFC的描述,行内盒子(Inline Box)由“行内级元素”(Inline Level Elements)和“文本”(Contents)所组成,而行内盒子(Inline Box)水平排列所构成的一行矩形区域,叫做“行盒子”(Line Box)。
对应实际场景的话,字体图标是一个display: inline-box的行内级元素,而“中文”这两个字是另外一个行内级元素,把这两个元素形成的两个行内盒子放在一起,构成了一个行盒子。
除了display: inline-box的元素和文本是行内级元素外,display属性为“inline”,“inline-table”的元素以及像“img”、“input”、“video”等“替换元素”(Replaced Elements)都是行内级元素,但他们在行盒子中的高度计算方式不太相同。
替换元素的高度,等于它们本身的高度(含margin),而非替换元素的高度,则稍微有点复杂。
这里,就要引出我们故事的主角:字体度量(Font Metrics)。

Font Metrics

简单来说,字体度量(Font Metrics)就是字体中的一系列参数,这些参数对于CSS来说是不可见的,所以我们需要借助一些工具来查看,比如FontForge
拿中文字体常用的“微软雅黑”为例,可以看到这些参数:

这里展示了一些基本信息,主要包括:
Em Size:单位字体所含的点的数量。也就是说,假设我们给字体设置的大小为100px,那在微软雅黑中,每个点分得 100 / 2048 的像素值。
Ascent:字符baseline到字符顶部的距离;
Descent:字符baseline到字符底部的距离;
通过Em Size,我们也就可以求得Ascent与Descent在实际场景中的具体像素值。但是,这里的Ascent与Descent只是字体的通用属性,实际渲染出来,往往会在字体的上下增加一些空间,而且在不同操作系统环境中,还不尽相同,如下图:
这里的Win Ascent与Win Descent是Windows系统下的值,而HHead Ascent与HHead Descent是Mac OS系统下的值。
此外,Capital Height代表该字体大写字母的高度,X Height可以理解为字体的小写字母x的高度。
下面借用一张图,来更清晰的展示各个值之间的关系:
如果细心的话会发现,这里的Ascent + Descent值居然超过了Em Size?
没错,这也就说明,在实际的场景中,当给一个字体设置100px大小时,它的高度并不一定等于100px,比如这里的话,就等于100 / 2048 * (2167 + 536) ≈ 132px。

Line Height

上面一节讲到了,字体实际渲染的高度与所设定的字体大小不一致。那么,对应到浏览器中,这个高度是什么高度呢?
想必,你已经猜到了,这个就是默认的行高,也就是当line-height: normal时的字体行高:

可以看到,这里的行高值跟我们之前计算出来的高度是一致的。或者,也可以这样理解:对于微软雅黑字体,设置 line-height: normal大致等于 line-height: 1.32
当行高的值大于 line-height: normal的值时,行间距(Leading)为正值;当行高的值小于 line-height: normal的值时,行间距(Leading)为负值。
一般情况下,应该避免 line-height设置过小,否则行与行之间的文本可能会出现重叠。如果我们给部分的字体设置了背景色,由于背景色是覆盖文本的实际高度的,这样会造成视觉效果更糟糕:

Vertical Align

对于vertical-align这个css属性,我曾经对它的实际作用苦恼了很久,比如:middle相对什么对齐?toptext-top又有什么区别?baseline究竟在哪里等等。
那我们就结合官方定义,来一个个看一下:

baseline

官方描述是,将行内盒子的baseline与其父级盒子的baseline对齐;如果行内盒子没有baseline,就将它底部的margin边界与父级盒子baseline对齐。
从前面的章节可以看出,对于文本来说,它的baseline的高度,其实就是字体度量中Descent的高度。而对于替换元素,就如后半句的定义,以底部margin的边界来对齐。
那么问题来了,父级盒子的baseline在哪里呢?
官方解释说,每个行盒子里,最开始会有一个不可见的、零宽度的行内盒子,官方称它叫“strut”。这个strut,可以看成一个普通的文本,字体为父级盒子所设置的font-family属性。所以,vertical-align其实就是与这个隐藏的文本strut的对应参考线对齐。

middle

vertical-align: middle是大家在处理垂直居中时,最常用到的属性。然而,它有时候会出现很多怪异的场景,比如父级元素高度增加了。
还是先来看下官方定义:将行内盒子垂直方向的中点与父级盒子的baseline以上的小写字母x的一半的高度对齐。
简单来说,就是参照小写字母x的一半的那条线,将垂直高度的一半与之对齐。
由此可以想象,设置了vertical-align: middle的行内盒子的相对位置会下降,在高度不变的基础上,父级盒子便会高出它下降的这段高度。
根据定义,可以很简单的算出来:(XHeight / 2 + Descent) - [(Ascent + Descent) / 2 - Descent],即 [(1106 / 2 + 536) - (2703 / 2 - 536)] * (100 / 2048) ≈ 13px。

此外,由于不同字体的Ascent / Descent的值不同,设置了 vertical-align: middle后,未必从视觉上看,是一定对齐的。这也是文章最开始中,图标跟字体看起来,还没有完全对齐的原因之一。

其他

关于toptext-topbottomtext-bottom等的区别,这里就不一一展开了,具体可以参考官方定义文档,或者参考下图:

字体与文本对齐

好了,绕了那么大一圈,现在我们回过来分析一下,最开始字体图标对齐的问题。
字体图标跟普通字体一样,同样可以用FontForge来查看:

可以看到,Descent的值为0。也就是说,字体图标的baseline的高度为0,从而使得与文本放在一起时,视觉上字体图标是偏上的,也就是第一张图所示的情况。
当对字体添加 vertical-align: middle之后,根据定义,字体图标会和小写字母x对齐:
但如果是中文的话,默认情况下,中文文本并没有与小写字母x垂直居中对齐,自然与字体图标看上去也不是居中对齐的。
于是,当对中文文本设置了 vertical-align: middle之后,由于两遍同时以自己的垂直中线与同一条线对齐,视觉上相对就对齐了。

果真如此吗?

如果眼见够尖会发现,其实此时的字体图标还是略微偏上了。
仔细检查一番,发现这个字体图标并没有撑满其内容区域:

这是因为,在制作该图形SVG的时候,线条并没有撑满画布(鞭打一番设计师)。修改之后,终于完全对齐了:

但是!!

这样似乎还有问题:
其一,前面也提到过,设置vertical-align: middle,可能带来父级元素高度增加的副作用;
其二,每次使用时,都必须对字体图标和文本添加vertical-align: middle,十分繁琐;
其三,对于多段文本中穿插字体图标的情况,这一解决方案就不适用了。 在理想状态下,默认对齐时,字体图标与文本视觉上就应该是对齐的。
根据前文的分析,如果想要视觉上尽可能对齐,对字体图标设置一个合适的Descent是关键。
如果针对英文环境,这个相对容易一些:因为大写英文字母,全部在baseline之上,且高度相对一致。部分小写字母,会下探到baseline以下,如g、y,但总的字体高度也大致相同。参考字体生成平台icomoon,其默认的baseline高度为6.28%em,也就是对于1024个单位的字体,baseline设置为64个单位。

中文环境相对麻烦一些,一方面不同汉字相对baseline下探的高度略有差异,另一方面,还要确保仍然和英文字母对齐。这里,主要参考了一些主流框架,设置了12.5%em的baseline高度,视觉上基本能令人满意:

总结

IFC相关知识一直以来都是css中的一大难点,在面试中也经常会涉及。
可能有些情况,尝试了几下就解决了,比如本人一开始的做法,对字体图标和文本同时添加vertical-align: middle,但如果没有彻底弄清其中的原理的话,往往采用的并不是最佳方式,甚至还会遇到一些“奇怪”的问题,比如高度增加。
希望通过本文,能帮助大家理解IFC,同时下面也列出了一些很好的参考文章供大家参考。本人在写作中,也借助这些文章,进一步加深并巩固了相关知识。

参考资料

Deep dive CSS: font metrics, line-height and vertical-align
css行高line-height的一些深入理解及应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值