图片来源: https://unsplash.com/photos/G...
本文做者:冯昊
垂直居中基本上是入门 CSS 必需要掌握的问题了,咱们确定在各类教程中都看到过“CSS 垂直居中的 N 种方法”,一般来讲,这些方法已经能够知足各类使用场景了,然而当咱们碰到了须要使用某些特殊字体进行混排、或者使文字对齐图标的状况时,也许会发现,不管使用哪一种垂直居中的方法,老是感受文字向上或向下偏移了几像素,不得不专门对它们进行位移,为何会出现这种状况呢?css
下图是一个使用各类常见的垂直居中的方法来居中文字的示例,其中涉及到不一样字体的混排,能够看出,虽然这里面用了几种经常使用的垂直居中的方法,可是在实际的观感上这些文字都没有刚好垂直居中,有些文字看起来比较居中,而有些文字则偏移得很厉害。
在线查看:CodePen(字体文件直接引用了谷歌字体,若是没有效果须要注意网络状况)html
经过设置vertical-align:middle
对文字进行垂直居中时,父元素须要设置font-size: 0
,由于vertical-align:middle
是将子元素的中点与父元素的baseline + x-height / 2
的位置进行对齐的,设置字号为 0 能够保证让这些线的位置都重合在中点。
咱们用鼠标选中这些文字,就能发现选中的区域确实是在父层容器里垂直居中的,那么为何文字却各有高低呢?这里就涉及到了字体自己的构造和相关的度量值。
这里先提出一个问题,咱们在 CSS 中给文字设置了 font-size
,这个值实际设置的是字体的什么属性呢?
下面的图给出了一个示例,文字所在的标签均为 span
,对每种字体的文字都设置了红色的 outline
以便观察,且设有 line-height: normal
。从图中能够看出,虽然这些文字的字号都是 40px,可是他们的宽高都各不相同,因此字号并不是设置了文字实际显示的大小。
为了解答这个问题,咱们须要对字体进行深刻了解,如下这些内容是西文字体的相关概念。首先一个字体会有一个 EM Square(也被称为 UPM、em、em size)[4],这个值最初在排版中表示一个字体中大写 M 的宽度,以这个值构成一个正方形,那么全部字母均可以被容纳进去,此时这个值实际反映的就成了字体容器的高度。在金属活字中,这个容器就是每一个字符的金属块,在一种字体里,它们的高度都是统一的,这样每一个字模均可以放入印刷工具中并进行排印。在数码排印中,em 是一个被设置了大小的方格,计量单位是一种相对单位,会根据实际字体大小缩放,例如 1000 单位的字体设置了 16pt 的字号,那么这里 1000 单位的大小就是 16pt。Em 在 OpenType 字体中一般为 1000 ,在 TrueType 字体中一般为 1024 或 2048(2 的 n 次幂)。前端
金属活字,图片来自 http://designwithfontforge.com/en-US/The_EM_Square.html
字体自己还有不少概念和度量值(metrics),这里介绍几个常见的概念,以维基百科的这张图为例(下面的度量值的计量单位均为基于 em 的相对单位):git
接下来咱们在 FontForge 软件里看看这些值的取值,这里以 Arial
字体给出一个例子:
从图中能够看出,在 General 菜单中,Arial 的 em size 是 2048,字体的 ascent 是1638,descent 是410,在 OS/2 菜单的 Metrics 信息中,能够获得 capital height 是 1467,x height 为 1062,line gap 为 67。
然而这里须要注意,尽管咱们在 General 菜单中获得了 ascent 和 descent 的取值,可是这个值应该仅用于字体的设计,它们的和永远为 em size;而计算机在实际进行渲染的时候是按照 OS/2 菜单中对应的值来计算,通常操做系统会使用 hhea(Horizontal Header Table)表的 HHead Ascent 和 HHead Descent,而 Windows 是个特例,会使用 Win Ascent 和 Win Descent。一般来讲,实际用于渲染的 ascent 和 descent 取值要比用于字体设计的大,这是由于多出来的区域一般会留给注音符号或用来控制行间距,以下图所示,字母顶部的水平线即为第一张图中 ascent 高度 1638,而注音符号均超过了这个区域。根据资料的说法[5],在一些软件中,若是文字内容超过用于渲染的 ascent 和 descent,就会被截断,不过我在浏览器里实验后发现浏览器并无作这个截断(Edge 86.0.608.0 Canary (64 bit), MacOS 10.15.6)。
在本文中,咱们将后面提到的 ascent 和 descent 均认为是 OS/2 选项中读取到的用于渲染的 ascent 和 descent 值,同时咱们将 ascent + descent 的值叫作 content-area。github
理论上一个字体在 Windows 和 MacOS 上的渲染应该保持一致,即各自系统上的 ascent 和 descent 应该相同,然而有些字体在设计时不知道出于什么缘由,致使其确实在两个系统中有不一样的表现。如下是 Roboto 的例子:
Differences between Win and HHead metrics cause the font to be rendered differently on Windows vs. iOS (or Mac I assume) · Issue #267 · googlefonts/roboto
那么回到本节一开始的问题,CSS 中的font-size
设置的值表示什么,想必咱们已经有了答案,那就是一个字体 em size 对应的大小;而文字在设置了line-height: normal
时,行高的取值则为 content-area + line-gap,即文本实际撑起来的高度。
知道了这些,咱们就不难算出一个字体的显示效果,上面 Arial 字体在line-height: normal
和font-size: 100px
时撑起的高度为(1854 + 434 + 67) / 2048 * 100px = 115px
。
在实验中发现,对于一个行内元素,鼠标拉取的 selection 高度为当前行line-height
最高的元素值。若是是块状元素,当line-height
的值为大于 content-area 时,selection 高度为line-height
,当其小于等于 content-area 时,其高度为 content-area 的高度。
在中间插一个问题,咱们应该都使用过 line-height
来给文字进行垂直居中,那么 line-height
实际是以字体的哪一个部分的中点进行计算呢?为了验证这个问题,我新建了一个颇有“设计感”的字体,em size 设为 1000,ascent 为 800,descent 为 200,并对其分别设置了正常的和比较夸张的 metrics:
上面图中左边是 FontForge 里设置的 metrics,右边是实际显示效果,文字字号设为 100px,四个字母均在父层的 flex 布局下垂直居中,四个字母的 line-height
分别为 0、1em、normal、3em,红色边框是元素的 outline
,黄色背景是鼠标选取的背景。由上面两张图能够看出,字体的 metrics 对文字渲染位置的影响仍是很大的。同时能够看出,在设置 line-height
时,虽然 line gap 参与了撑起取值为 normal
的空间,可是不参与文字垂直居中的计算,即垂直居中的中点始终是 content-area 的中点。
咱们又对字体进行了微调,使其 ascent 有必定偏移,这时能够看出 1em 行高的文字 outline 刚好在正中间,所以能够得出结论:在浏览器进行渲染时,em square 老是相对于 content-area 垂直居中。
说完了字体构造,又回到上一节的问题,为何不一样字体文字混排的时候进行垂直居中,文字各有高低呢?
在这个问题上,本文给出这样一个结论,那就是由于不一样字体的各项度量值均不相同,在进行垂直居中布局时,content-area 的中点与视觉的中点不统一,所以致使实际看起来存在位置偏移,下面这张图是 Arial 字体的几个中线位置:
从图上能够看出来,大写字母和小写字母的视觉中线与整个字符的中线仍是存在必定的偏移的。这里我没有找到排版相关学科的定论,究竟以哪条线进行居中更符合人眼观感的居中,以我我的的观感来看,大写字母的中线可能看起来更加舒服一点(尤为是与没有小写字母的内容进行混排的时候)。web
须要注意一点,这里选择的 Arial 这个字体自己的偏移比较少,因此使用时总体感受仍是比较居中的,这并不表明其余字体也都是这样。
对于中文字体,自己的设计上没有基线、升部、降部等说法,每一个字都在一个方形盒子中。可是在计算机上显示时,也在必定程度上沿用了西文字体的概念,一般来讲,中文字体的方形盒子中文字体底端在 baseline 和 descender 之间,顶端超出一点 ascender,而标点符号正好在 baseline 上。typescript
咱们已经了解了字体的相关概念,那么如何解决在使用字体时出现的偏移问题呢?
经过上面的内容能够知道,文字显示的偏移主要是视觉上的中点和渲染时的中点不一致致使的,那么咱们只要把这个不一致修正过来,就能够实现视觉上的居中了。
为了实现这个目标,咱们能够借助 vertical-align
这个属性来完成。当 vertical-align
取值为数值的时候,该值就表示将子元素的基线与父元素基线的距离,其中正数朝上,负数朝下。
这里介绍的方案,是把某个字体下的文字经过计算设置 vertical-align
的数值偏移,使其大写字母的视觉中点与用于计算垂直居中的点重合,这样字体自己的属性就再也不影响居中的计算。
具体咱们将经过如下的计算方法来获取:首先咱们须要已知当前字体的 em-size,ascent,descent,capital height 这几个值(若是不知道 em-size,也能够提供其余值与 em-size 的比值),如下依然以 Arial 为例:api
const emSize = 2048; const ascent = 1854; const descent = 434; const capitalHeight = 1467; // 计算前须要已知给定的字体大小 const fontSize = FONT_SIZE; // 根据文字大小,求得文字的偏移 const verticalAlign = ((ascent - descent - capitalHeight) / emSize) * fontSize; return ( <span style={{ fontFamily: FONT_FAMILY, fontSize }}> <span style={{ verticalAlign }}>TEXT</span> </span> )
由此设置之后,外层 span 将表现得像一个普通的可替换元素参与行内的布局,在必定程度上无视字体 metrics 的差别,可使用各类方法对其进行垂直居中。
因为这种方案具备固定的计算步骤,所以能够根据具体的开发需求,将其封装为组件、使用 CSS 自定义属性或使用 CSS 预处理器对文本进行处理,经过传入字体信息,就能修正文字垂直偏移。浏览器
虽然上述的方案能够在必定程度上解决文字垂直居中的问题,可是在实际使用中还存在着不方便的地方,咱们须要在使用字体以前就知道字体的各项 metrics,在自定义字体较少的状况下,开发者能够手动使用 FontForge 等工具查看,然而当字体较多时,挨个查看仍是比较麻烦的。
目前的一种思路是咱们可使用 Canvas 获取字体的相关信息,如如今已经有开源的获取字体 metrics 的库 FontMetrics.js。它的核心思想是使用 Canvas 渲染对应字体的文字,而后使用 getImageData
对渲染出来的内容进行分析。若是在实际项目中,这种方案可能致使潜在的性能问题;并且这种方式获取到的是渲染后的结果,部分字体做者在构建字体时并无严格将设计的 metrics 和字符对应,这也会致使获取到的 metrics 不够准确。
另外一种思路是直接解析字体文件,拿到字体的 metrics 信息,如 opentype.js 这个项目。不过这种作法也不够轻量,不适合在实际运行中使用,不过能够考虑在打包过程当中自动执行这个过程。
此外,目前的解决方案更可能是偏向理论的方法,当文字自己字号较小的状况下,浏览器可能并不能按照预期的效果渲染,文字会根据所处的 DOM 环境不一样而具备 1px 的偏移[9]。网络
CSS Houdini 提出了一个 Font Metrics 草案[6],能够针对文字渲染调整字体相关的 metrics。从目前的设计来看,能够调整 baseline 位置、字体的 em size,以及字体的边界大小(即 content-area)等配置,经过这些能够解决因字体的属性致使的排版问题。
[Exposed=Window] interface FontMetrics { readonly attribute double width; readonly attribute FrozenArray<double> advances; readonly attribute double boundingBoxLeft; readonly attribute double boundingBoxRight; readonly attribute double height; readonly attribute double emHeightAscent; readonly attribute double emHeightDescent; readonly attribute double boundingBoxAscent; readonly attribute double boundingBoxDescent; readonly attribute double fontBoundingBoxAscent; readonly attribute double fontBoundingBoxDescent; readonly attribute Baseline dominantBaseline; readonly attribute FrozenArray<Baseline> baselines; readonly attribute FrozenArray<Font> fonts; };
从 https://ishoudinireadyyet.com/ 这个网站上能够看到,目前 Font Metrics 依然在提议阶段,还不能肯定其 API 具体内容,或者之后是否会存在这一个特性,所以只能说是一个在将来也许可行的文字排版处理方案。
文本垂直居中的问题一直是 CSS 中最多见的问题,可是却很难引发注意,我我的以为是由于咱们经常使用的微软雅黑、苹方等字体自己在设计上比较规范,在一般状况下都显得比较居中。可是当一个字体不是那么“规范”时,传统的各类方法彷佛就有点无能为力了。
本文分析了致使了文字偏移的因素,并给出寻找文字垂直居中位置的方案。
因为涉及到 IFC 的问题自己就很复杂[7],关于内联元素使用 line-height
与 vertical-align
进行居中的各类小技巧由于与本文不是强相关,因此在文章内也没有说起,若是对这些内容比较感兴趣,也能够经过下面的参考资料寻找一些相关介绍。
本文发布自 网易云音乐大前端团队,文章未经受权禁止任何形式的转载。咱们常年招收前端、iOS、Android,若是你准备换工做,又刚好喜欢云音乐,那就加入咱们 grp.music-fe(at)corp.netease.com!