深入了解魔性的CSS字体

5,947 阅读8分钟

最近在开发遇到了两个一直困扰我的问题:

  • 当 font-family 为 PingFangSC-Regular 时,为什么设置了 font-weight 为 500 和 font-weight 为 400 的现象一样?
  • 某些Android 系统手机中,中文和数字/英文同时设置了 font-weight 为 500,为什么只有数字/英文实现了加粗?

看到这两个神奇的现象的时候,让我陷入了深深的沉思,原来我撸了这么多代码,还是没能真正了解字体。不甘心被这魔性的字体困扰,发誓一定要拿出一个解决方案,在不断翻箱倒柜查阅资料之后,我想我现在开始有点懂了。

首先科普一下字体相关的知识

什么是字体

在字体排印学中,字体(英语:typeface)是由一个或多个字型组成的集合,每个字型由具有共同设计特征的字形组成。字体的每一种字型都有特定的字重(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 个常用的字体名称:  serifsans-serifmonospacecursive,和  fantasy。  这些都是非常通用的,当使用这些通用名称时,使用的字体完全取决于每个浏览器,而且它们所运行的每个操作系统也会有所不同。这是一种糟糕的情况,浏览器会尽力提供一个看上去合适的字体。 serifsans-serif  和  monospace  是比较好预测的,默认的情况应该比较合理,另一方面,cursive  和  fantasy  是不太好预测的,我们建议使用它们的时候应该稍微注意一些,多多测试。 ---MDN

什么是字重/字重的回退机制

在 CSS 中,可以通过 font-weight 属性指定了字体的粗细程度。其属性值既可以用 normal,bold 等粗细值名称表示,也可以用介于 1-1000 之间的数值表示,同时数值采取离散式表示,非 100 的整数倍的数值将被四舍五入转换为 100 的倍数。下面是一些常见粗细值名称及其对应的数值

数值粗细值名称
100Thin (Hairline)
200Extra Light (Ultra Light)
300Light
400Normal
500Medium
600SemiBold (Demi Bold)
700Bold
800Extra Bold (Ultra Bold)
900Black (Heavy)

但是不同字体族/字体支持的字重不同,如果指定的权重值不可用,浏览器是如何解决的呢?没错,就是靠字重的回退机制去解决。

如果指定的权重值在  400和  500之间(包括400500):

  • 按升序查找指定值与500之间的可用权重;
  • 如果未找到匹配项,按降序查找小于指定值的可用权重;
  • 如果未找到匹配项,按升序查找大于500的可用权重。

如果指定值小于400

  • 降序查找小于指定值的可用权重。 如果未找到匹配项,按升序查找大于指定值的可用权重(先尽可能的小,再尽可能的大)。

如果指定值大于500

  • 升序查找大于指定值的可用权重。 如果未找到匹配项,按降序查找小于指定值的可用权重(先尽可能的大,再尽可能的小)。

现在给大家说明问题产生的原因

科普完知识完之后,接下来给大家说明一下开头提出的两个问题的原因。

PingFangSC-Regular 的字重问题

目前PingFang SC字体族提供了ThinUltralightLightRegularMediumSemibold这 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 的字重,只有数字/英文呈现了变粗的效果。

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>

ps:用手上仅有的华为测试机在不同的浏览器/不同 APP 的 webview 下看,大部分都是支持的。对于这个问题的解决,欢迎有其他方案的小伙伴也可以评论区提出。

结语

一人之力微薄,有错误/缺漏之处在所难免,欢迎 👇 指出。希望阅读完本文的小伙伴们能对字体有一定的了解,也希望这边文章能对大家后续的开发工作有帮助。

参考链接

引导关注.gif