最近在开发遇到了两个一直困扰我的问题:
看到这两个神奇的现象的时候,让我陷入了深深的沉思,原来我撸了这么多代码,还是没能真正了解字体。不甘心被这魔性的字体困扰,发誓一定要拿出一个解决方案,在不断翻箱倒柜查阅资料之后,我想我现在开始有点懂了。
在字体排印学中,字体(英语:typeface)是由一个或多个字型[1]组成的集合,每个字型由具有共同设计特征的字形[2]组成。字体的每一种字型都有特定的字重(weight)、风格(style)、宽度(width)、倾斜度(slant)、斜体(italicization)、装饰(ornamentation)、设计师或铸字厂 --- 维基百科
在 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 的倍数。下面是一些常见粗细值名称及其对应的数值
数值 | 粗细值名称 |
---|---|
100 | Thin (Hairline) |
200 | Extra Light (Ultra Light) |
300 | Light |
400 | Normal |
500 | Medium |
600 | SemiBold (Demi Bold) |
700 | Bold |
800 | Extra Bold (Ultra Bold) |
900 | Black (Heavy) |
但是不同字体族/字体支持的字重不同,如果指定的权重值不可用,浏览器是如何解决的呢?没错,就是靠字重的回退机制去解决。
如果指定的权重值在
400
和500
之间(包括400
和500
):
- 按升序查找指定值与
500
之间的可用权重;- 如果未找到匹配项,按降序查找小于指定值的可用权重;
- 如果未找到匹配项,按升序查找大于
500
的可用权重。如果指定值小于
400
- 按降序查找小于指定值的可用权重。如果未找到匹配项,按升序查找大于指定值的可用权重(先尽可能的小,再尽可能的大)。
如果指定值大于
500
,
- 按升序查找大于指定值的可用权重。如果未找到匹配项,按降序查找小于指定值的可用权重(先尽可能的大,再尽可能的小)。
科普完知识完之后,接下来给大家说明一下开头提出的两个问题的原因。
目前PingFang SC
字体族提供了Thin
,Ultralight
,Light
,Regular
,Medium
,Semibold
这 6 种字重的字体。而PingFangSC-Regular
(平方-简 常规体)相当于PingFang SC
字体族下面 400 的字重。 初步验证了下,PingFangSC-Regular
应该是不支持 500 字重的,如果设置了 font-weight 为 500,按照字重的回退机制,就会匹配到 400,所以就会有最开头提出的问题:为何设置字重为 500 和 400 的表现一样,没有看到任何粗细的变化。
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 的字重,只有数字/英文呈现了变粗的效果。
ps:Android 手机环境复杂,厂商居多,表现上存在巨大差异,上面仅仅作为说明问题的产生,不代表每个 Android 手机都存在这个问题。
在了解了字体的 fallback 机制以及安全字体的原理之后,我们就可以合理设计移动端字体的 fallback 机制。 一般的 IOS/MAC 上,都不忍割舍掉这么好看的PingFang SC
[3]字体,为了照顾 IOS 用户有更好的体验,所以我们可以首选PingFang SC
字体族。对于 IOS9/macOS10.11 以下不支持PingFang SC
字体族的版本,我们需要向下兼容,使用Helvetica/Neue Helvetica
[4]字体备选,在 win 系统中,我们可以使用无无衬线西文字体Arial
[5],最后指定安全字体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, Arial, 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
}
翻找了几天资料,发现Noto Sans SC[6]字体能支持中文/数字/英文字体 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>
ps:用手上仅有的华为测试机在不同的浏览器/不同 APP 的 webview 下看,大部分都是支持的。对于这个问题的解决,欢迎有其他方案的小伙伴也可以评论区提出。
一人之力微薄,有错误/缺漏之处在所难免,欢迎 指出。希望阅读完本文的小伙伴们能对字体有一定的了解,也希望这篇文章能对大家后续的开发工作有帮助。
[1]字型: https://zh.wikipedia.org/wiki/%E5%AD%97%E5%9E%8B
[2]字形: https://zh.wikipedia.org/wiki/%E5%AD%97%E5%BD%A2
[3]PingFang SC
: https://zh.wikipedia.org/wiki/%E8%8B%B9%E6%96%B9
[4]Helvetica/Neue Helvetica
: https://zh.wikipedia.org/wiki/Helvetica
[5]Arial
: https://zh.wikipedia.org/wiki/Arial
[6]Noto Sans SC: https://fonts.google.com/specimen/Noto+Sans+SC#standard-styles
[7]font-family 详解: https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family
[8]font-weight 详解: https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-weight
[9]安全字体: https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_text/Fundamentals#default_fonts
[10]字体: https://zh.wikipedia.org/wiki/%E5%AD%97%E4%BD%93
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8