深入了解魔性的CSS字体
最近在开发遇到了两个一直困扰我的问题:
- 当 font-family 为 PingFangSC-Regular 时,为什么设置了 font-weight 为 500 和 font-weight 为 400 的现象一样?
- 在某些Android 系统手机中,中文和数字/英文同时设置了 font-weight 为 500,为什么只有数字/英文实现了加粗?
看到这两个神奇的现象的时候,让我陷入了深深的沉思,原来我撸了这么多代码,还是没能真正了解字体。不甘心被这魔性的字体困扰,发誓一定要拿出一个解决方案,在不断翻箱倒柜查阅资料之后,我想我现在开始有点懂了。
首先科普一下字体相关的知识
什么是字体
在字体排印学中,字体(英语:typeface)是由一个或多个字型[1]组成的集合,每个字型由具有共同设计特征的字形[2]组成。字体的每一种字型都有特定的字重(weight)、风格(style)、宽度(width)、倾斜度(slant)、斜体(italicization)、装饰(ornamentation)、设计师或铸字厂 --- 维基百科
什么是字体 fallback 机制
在 css 中,可以通过 font-family 指定不同的字体,并且可以给定一个先后顺序,由字体名或者字体族名组成。当指定的的字体找不到的时候,浏览器会按照 font-family 属性指定的先后顺序寻找支持的字体。比如:
html {
font-family: 'PingFang SC', sans-serif;
}
在上面的 CSS 代码中,指定PingFang SC
的字体族和通用字体sans-serif
,在支持平方字体族的 Mac/IOS 平台上用平方的字体,在不支持的平方字体的 Android 等平台上,会命中sans-serif
,如果sans-serif
也不支持,就会默认用浏览器的默认字体代替。
什么是安全字体
说到字体可用性,只有某几个字体通常可以应用到所有系统,因此可以毫无顾忌地使用。这些都是所谓的 网页安全字体。 ---MDN
CSS 定义了 5 个常用的字体名称:serif
,sans-serif
,monospace
,cursive
,和fantasy
。 这些都是非常通用的,当使用这些通用名称时,使用的字体完全取决于每个浏览器,而且它们所运行的每个操作系统也会有所不同。这是一种糟糕的情况,浏览器会尽力提供一个看上去合适的字体。serif
,sans-serif
和monospace
是比较好预测的,默认的情况应该比较合理,另一方面,cursive
和fantasy
是不太好预测的,我们建议使用它们的时候应该稍微注意一些,多多测试。 ---MDN
什么是字重/字重的回退机制
在 CSS 中,可以通过 font-weight 属性指定了字体的粗细程度。其属性值既可以用 normal,bold 等粗细值名称表示,也可以用介于 1-1000 之间的数值表示,同时数值采取离散式表示,非 100 的整数倍的数值将被四舍五入转换为 100 的倍数。下面是一些常见粗细值名称及其对应的数值
![](https://pic1.zhimg.com/80/v2-e75851e82709992f83463cf1797a127c_720w.webp)
但是不同字体族/字体支持的字重不同,如果指定的权重值不可用,浏览器是如何解决的呢?没错,就是靠字重的回退机制去解决。
如果指定的权重值在400
和500
之间(包括400
和500
):
- 按升序查找指定值与
500
之间的可用权重; - 如果未找到匹配项,按降序查找小于指定值的可用权重;
- 如果未找到匹配项,按升序查找大于
500
的可用权重。
如果指定值小于400
- 按降序查找小于指定值的可用权重。 如果未找到匹配项,按升序查找大于指定值的可用权重(先尽可能的小,再尽可能的大)。
如果指定值大于500
,
- 按升序查找大于指定值的可用权重。 如果未找到匹配项,按降序查找小于指定值的可用权重(先尽可能的大,再尽可能的小)。
现在给大家说明问题产生的原因
科普完知识完之后,接下来给大家说明一下开头提出的两个问题的原因。
PingFangSC-Regular 的字重问题
目前PingFang SC
字体族提供了Thin
,Ultralight
,Light
,Regular
,Medium
,Semibold
这 6 种字重的字体。而PingFangSC-Regular
(平方-简 常规体)相当于PingFang SC
字体族下面 400 的字重。 初步验证了下,PingFangSC-Regular
应该是不支持 500 字重的,如果设置了 font-weight 为 500,按照字重的回退机制,就会匹配到 400,所以就会有最开头提出的问题:为何设置字重为 500 和 400 的表现一样,没有看到任何粗细的变化。
Android 系统的中文字体的字重问题
Android 5.0 之后,几乎整个手机的字体效果都由 fonts.xml
这个配置文件来掌控。
//
<family name="sans-serif">
<font weight="100" style="normal">Roboto-Thin.ttf</font>
<font weight="300" style="normal">Roboto-Light.ttf</font>
<font weight="400" style="normal">Roboto-Regular.ttf</font
<font weight="500" style="normal">Roboto-Medium.ttf</font>
<font weight="700" style="normal">Roboto-Bold.ttf</font>
<font weight="900" style="normal">Roboto-Black.ttf</font>
<font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
...
<font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
</family>
上面这里代码例子可以看出,font.xml 控制了 Android 操作系统在不同 UI 界面中的字体粗细,告诉系统该调用什么字体。结合下面的图片,我们可以看出,在某些Android 系统中,对于数字/英文字体都是支持 100-900 内多种字重的,但是发现对于中文字体而言,仅支持 Normal 和 Bold 两种字重,所以才会出现同时给数字/英文和中文设置 500 的字重,只有数字/英文呈现了变粗的效果。
![](https://pic1.zhimg.com/80/v2-0c347c09bc974ff7ea1a399029d0eaf0_720w.webp)
ps:Android 手机环境复杂,厂商居多,表现上存在巨大差异,上面仅仅作为说明问题的产生,不代表每个 Android 手机都存在这个问题。
那么我们应该怎么做
合理的设计移动端字体的 fallback 机制
在了解了字体的 fallback 机制以及安全字体的原理之后,我们就可以合理设计移动端字体的 fallback 机制。 一般的 IOS/MAC 上,都不忍割舍掉这么好看的PingFang SC
字体,为了照顾 IOS 用户有更好的体验,所以我们可以首选PingFang SC
字体族。对于 IOS9/macOS10.11 以下不支持PingFang SC
字体族的版本,我们需要向下兼容,使用Helvetica/Neue Helvetica
字体备选,在 win 系统中,我们可以使用无无衬线西文字体Arial
,最后指定安全字体sans-serif
为兜底字体。 原生 Android 下中文字体与英文字体都选择默认的无衬线字体。4.0 之前版本英文字体原生 Android 使用的是 Droid Sans,中文字体原生 Android 会命中 Droid Sans Fallback。4.0+ 中英文字体都会使用原生 Android 新的 Roboto 字体。所以在 Android 系统中个,一般默认其命中系统字体 最终我们可以得到下面的一个 fallback 机制。
//
html {
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, v, sans-serif
}
规范的开发
作为移动端前端开发,在实现字体加粗的时候,应当避免直接使用 font-family 指定字体实现,比如下面的代码,直接指定了PingFang-SC-Medium
来实现字重为 500 的加粗效果,但是这样在不支持PingFang SC
字体族的系统/机型来说,不仅没有实现了加粗效果,反而还破坏了字体的 fallback 机制。
// wrong
.title {
font-family: PingFang-SC-Medium;
}
对于设置的 fallback 字体,如果都支持 500 的字重的字体,这时候我们可以通过设置字重实现,例如:
// Correct
.title {
font-weight: 500;
}
对于设置的 fallback 字体,如果存在不支持 500 的字重的字体,但是又希望实现加粗,这个时候需要重新改写 fallback 来实现。例如 Arial 如果不支持 500 字重,就可以使用 Arial Bold 字体来实现加粗:
// Correct
.title {
font-weight: 500;
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica,Arial Bold, sans-serif
}
如何解决 Android 系统中文字体字重问题
翻找了几天资料,发现Noto Sans SC字体能支持中文/数字/英文字体 100,300,400,500,700,900 的字重,具体如下图所示:
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC:100,300,400,500,700,900">
</link>
<style>
.title {
font-family: Noto Sans SC;
}
</style>
![](https://pic3.zhimg.com/80/v2-c591f4faf455dd76e7887f14db678066_720w.webp)
ps:用手上仅有的华为测试机在不同的浏览器/不同 APP 的 webview 下看,大部分都是支持的。对于这个问题的解决,欢迎有其他方案的小伙伴也可以评论区提出。