图片来源:https://unsplash.com/photos/G...本文作者:冯浩1.简介的垂直居中基本是必须解决的问题CSS入门时掌握。我们一定在各种教程中看过《CSS垂直居中的N种方法》。总的来说,这些方法已经可以满足各种使用场景。但是,当我们遇到需要使用一些特殊字体进行混合布局,或者将文字与图标对齐时,你可能会发现,无论你使用哪种垂直居中方式,你总是感觉文字上下移动了几个像素,你必须专门移动它们。为什么会这样?情况如何?2.常见的垂直居中方法下图是使用各种常见的垂直居中方法对文本进行居中的例子,其中涉及到不同字体的混合。可以看出,虽然这里使用了几种常用的垂直居中方式居中的方法,但是在实际感知中,这些文字并没有完全垂直居中。有些文本看起来更居中,而有些文本偏移很大。在线查看:CodePen(字体文件直接参考谷歌字体,如果没有效果,需要注意网络情况)通过设置vertical-align:middle使文本垂直居中时,父元素需要设置字体-size:0,因为vertical-align:middle将子元素的中点与父元素的baseline+x-height/2的位置对齐,设置fontsize为0保证这些线的位置重合中点。当我们用鼠标选中这些文字时,可以发现选中的区域在父层容器中确实是垂直居中的,那为什么文字的高度不一样呢?这涉及字体本身和相关指标的构建。3.字体的结构和度量这里先提一个问题,我们在CSS中给文本设置了font-size,这个值到底设置的是什么字体属性呢?下图显示了一个示例。放置文字的标签都是span,并且为每个字体都设置了一个红色的轮廓,方便观察,并且设置了line-height:normal。从图中可以看出,虽然这些文字的字号都是40px,但是它们的宽高是不一样的,所以字号并没有设置成文字实际显示的大小。为了回答这个问题,我们需要对字体有深入的了解。以下是西文字体的相关概念。首先,一个字体会有一个EMSquare(也称为UPM、em、em大小)[4]。这个值原本表示排版时字体中大写M的宽度。如果用这个值组成一个正方形,那么所有的字母都可以容纳,这个值实际上反映了字体容器的高度。在金属活字中,这个容器就是每个字的金属块,在字体中,它们的高度是统一的,这样每个活字都可以放在印刷工具和排版中。在数字排版中,em是具有固定大小的正方形。计量单位是相对单位,会根据实际字体大小进行缩放。比如1000个单位的字号设置为16pt,那么这里1000个单位的字号就是16pt。Em在OpenType字体中通常为1000,在TrueType字体中通常为1024或2048(2的n次方)。金属活字,图片来自http://designwithfontforge.com/en-US/The_EM_Square.html3.1字体测量字体本身有很多概念和度量(metrics),这里介绍几个常用的概念,根据维基百科取以这张图为例(下面的计量单位都是以em为基准的相对单位):baseline:基线(baseline)是放置字母的水平线。xheight:X高度(x字符高度)表示小写字母x在基线上的高度。大写高度:大写高度表示大写字母在基线上方的高度。ascender/上升:Ascender(升序部分)表示超过x字高度的小写字母的词干。为了便于识别,上升器的高度可能比大写高度稍大。Ascent表示从文本顶部到基线的距离。descender/descent:descender表示延伸到基线以下的小写字母的词干,如j、g等的底部。descent表示文本底部到基线的距离。linegap:linegap表示从下降的底部到下一条上升线的顶部的距离。这个词我还没有找到合适的中文翻译。需要注意的是这个值不是leading,是指两行文字的基线之间的距离。接下来我们看看这些值在FontForge软件中的值。这里以Arial字体为例:从图中可以看出,在General菜单中,Arial的em大小为2048,字体的ascent为1638,descent为410。在Metrics信息中OS/2菜单,可以得到capitalheight为1467,xheight为1062,linegap为67。不过这里需要注意的是,虽然我们在通用菜单,这个值应该只用于字体设计,它们的总和总是em大小;而电脑实际是按照OS/2菜单渲染的,一般操作系统会使用hhea(Horizo??ntalHeaderTable)表的HHeadAscent和HHeadDescent,而Windows是特例,会使用WinAscent和WinDescent.一般来说,实际用于渲染的ascent和descent值比用于字体设计的要大一些,因为多出的区域通常是预留注音符号或者用来控制行间距,如下图,最上面的字母的横线是第一张图中的上升高度1638,注音符号都超过了这个区域。根据资料[5],在一些软件中,如果文字内容超过了用于渲染的ascent和descent,就会被截断,但是我在浏览器中实验后发现,浏览器并没有做这个截断(Edge86.0.608.0金丝雀(64位),MacOS10.15.6)。在本文中,我们将后面提到的ascent和descent看作是从OS/2选项中读取的ascent和descent值进行渲染,我们称ascent+descent的值为content-area。从理论上讲,一种字体在Windows和MacOS上的渲染应该是一致的,即在各自系统上的上升和下降应该是相同的。但是有些字体不知道是为什么设计的,所以在两个系统中确实有不同的区别。表现。以下是Roboto的示例:Win和HHead指标之间的差异导致字体在Windows与iOS(或我假设的Mac)上呈现不同Issue#267googlefonts/roboto所以回到本节开头的问题,CSS中font-size设置的值是什么意思,想必我们已经有了答案,那就是一个字体em大小对应的大小;而当文本设置为line-height:normal时,行高的值为content-area+line-gap,即文本的实际高度。知道了这一点,我们就不难计算出一个字体的显示效果了。上面Arial字体在line-height:normal和font-size:100px时的高度为(1854+434+67)/2048*100px=115px。在实验中发现,对于一个行内元素,鼠标拉动选择的高度为当前行line-height最大的元素值。如果是块元素,当line-height的值大于content-area时,选择高度为line-height,小于等于content-area时,其高度为content-的高度区域。3.2验证metrics对文本渲染的影响中间插个问题,我们本来应该用line-height来让文本垂直居中的,那么到底是哪一部分字体算作line-height的中点呢?为了验证这个问题,我新建了一个很有“设计感”的字体,将em大小设置为1000,ascent设置为800,descent设置为200,并为其设置了正常和夸张的指标:leftin上图是在FontForge中设置的metrics,右边是实际显示效果,文字字体大小设置为100px,父层flex布局下四个字母垂直居中,line-height为四个字母分别是0、1em、normal、3em,红色边框是元素的轮廓,黄色背景是鼠标选中的背景。从上面两张图可以看出,fontmetrics对文本渲染位置的影响很大。同时可以看出,在设置line-height时,linegap虽然参与撑起空间,值为normal,但不参与文本垂直居中的计算,即中点垂直居中始终是内容区域的中点。我们还对字体进行了微调,使ascent有一定程度的偏移。这时我们可以看到1em行高的轮廓刚好在中间,所以我们可以断定浏览器渲染时,em方块总是相对于Verticallycenterthecontent-area。说完字体结构,我们再回到上一节的问题。为什么不同字体的字符混合后会垂直居中,字符的高度也不一样?关于这个问题,本文给出了这样一个结论,即由于不同字体的度量值不同,在进行垂直居中布局时,content-area的中点与视觉中点不一致,从而导致实际好像有位置偏移。下图是Arial字体的几个中心线位置:从图中可以看出,大小写字母的视觉中心线与整个字符的中心线还是有一定的偏移。在这里我还没有找到关于排版相关学科的结论。哪条线更符合人眼对居中的感知?个人感觉,大写字母的中线可能看起来更舒服(尤其是没有小写字母。当内容混合时)。需要注意的是,这里选择的Arial字体偏移量较小,所以使用起来整体感觉比较居中,不代表其他字体也是这样。3.3中文字体对于中文字体,设计本身没有基线、上升部分、下降部分等术语,每个字符都在一个方框内。但是在电脑上显示的时候,也在一定程度上使用了西方字体的概念。一般来说,汉字方框的底部在基线和下行之间,顶部稍微超出上行,而标点符号正好在基线上。4.CSS解决方案我们了解了字体的概念,那么在使用字体时如何解决偏移问题呢?从以上内容我们可以知道,文字显示偏移主要是视觉中点和渲染中点不一致造成的。那么我们只需要修正这种不一致就可以实现视觉居中了。为了达到这个目的,我们可以使用属性vertical-align来完成。当vertical-align的值为一个值时,该值表示子元素的基线与父元素的基线之间的距离,正数朝上,负数朝下。这里介绍的方案是通过计算某种字体下的文字来设置vertical-align的值偏移量,使大写字母的视觉中点与计算垂直居中的点重合,从而使字体的属性本身不再影响居中计算。具体我们会通过如下计算方式获取:首先,我们需要知道当前字体的em-size、ascent、descent、capitalheight(如果不知道em-size,也可以提供其他的valuesandem-sizeratio),下面仍然以Arial为例:constemSize=2048;常量上升=1854;const血统=434;constcapitalHeight=1467;//计算前需要知道给定的字体大小constfontSize=FONT_SIZE;//根据文字的大小,得到文字的偏移量constverticalAlign=((ascent-descent-capitalHeight)/emSize)*字体大小;return(
