字体基础
先理清几个概念
- 字符 Character:数字、字母、符号等, 一个图形实体。
- 字符集 Character set:字符的集合,但在风格形状上不一定统一。
- 字形 Glyph:特指某个字符的形状,可辨认的图形实体,同一个字符可以有很多字形。
- 字型 Font:源于铅字印刷,指一整套具有相同设计的字形集合,计算机领域的字型 Font 就特指某个字体文件(字库),比如一套黑体五号字。
- 字体 Typeface:当一系列风格统一的字型在一起,就形成体的概念,可以用字体来描述,比如 宋体。
随着矢量字体的出现,尺寸的概念逐渐模糊,『字型』和『字体』、Font 和 Typeface 也不再被那么严格区分,基本被当做一个意思。
字体分类
衬线
- 无衬线 sans serif:无衬线适合屏幕阅读、适用于科技类文章。
- 衬线 serif:衬线字体更为优雅隽秀,接近传统纸张阅读的感觉,印刷效果漂亮,适用于文学性的文本。
字符距离
- 比例字体 proportional,适合显示普通文本。
- 等宽字体 monospace,字符和标点排列工整,宽度相同,经常会对个别字母和符号做显示优化,适合展示代码。比如 『FiraCode』,『Input mono』等等。
字体构成
字体的构成主要包括轮廓格式、封装格式、编码方式三方面。
- 数据格式,用来描述字形。
- 编码方式,决定字符编号,字形对应关系。
- 封装格式,决定字体文件大小格式
重点在于数据格式,主要有:
- 点阵字体 bitmap(位图字体),分辨率低、不适合放大,渲染快,适用于低像素输出设备。
- 轮廓字体,通过贝塞尔曲线描述字形,向量图集合,适合缩放。
- 笔画字体。
除了一些特殊设备,目前普遍使用的是轮廓字体,主要有以下几种:
- PostScript(PS) 轮廓字体,Adobe 开发的,用三次贝塞尔曲线描述字形,效果好适合印刷打印,根据封装格式和编码方式不同又有 Type1、CID、Type0 等类型。
- TrueType(TT),苹果为对抗 Adobe,与微软共同开发,用二次贝塞尔曲线描述字形,渲染快,OSX 和 Windows 上最常见的字体。
- OpenType,微软先独自开发目标对抗 true type,之后 Adobe 加入,是 TrueType 的升级兼容版,也增加了对PostScript 轮廓的支持,常见有 OpenType layout & PostScript Outlines。
Web 字体
字体匹配
从 CSS1 开始就定义了字体匹配算法, CSS2 Fonts matching algorithm 、CSS Fonts Module Level 3 和 CSS Fonts Module Level 4 也先后做了一些修改演变。
但总体思路不变,要求 UA 先创建字体属性对应的数据库,然后对每个字符尝试匹配第一个 font-family
名字,再尝试匹配其余 font 属性。family name
的匹配也陆续支持匹配一组字体(非某个特定字体),支持本地化名称匹配。
而一旦找不到,就需要使用 Fallback 字体作为兜底。
Fallback 字体
不同的操作系统和浏览器搭配,会提供不同的默认字体。
具体到网页来说,影响页面默认 fallback 字体的因素包括:系统内置、浏览器配置、charset、lang 属性、font-family 中之前项的值等。
比如 windows 的相关机制:
系统字体
css 中可以用 system-ui
来选择当前操作系统的默认字体,这样可以让 web 页面的文字视觉风格跟原生 App 一致。
在兼容性方面,除了 IE,最近的主流浏览器都支持 system-ui
,OSX 10.11 在之前也支持-apple-system
的兼容写法。
由于无衬线字体更适合屏幕阅读,所以各个系统一般都会带有无衬线字体 sans-serif
并做为最后兜底。
OSX 上默认西文字体早期有 Helvetica Neue ,目前是 San Francisco 。Windows 上尽管 system-ui
有兼容性问题,但默认西文字体还是有的,比如早期的 Microsoft Sans Serif,目前的 Segoe UI。Andorid 上是 Roboto。
但对于中文字体呢?如果不设置中文字体,显示会有很多不同。
比如Windows 上无论是否衬线,早期 汉字默认最终都会落到『宋体』(中易宋体),现在一般是『微软雅黑』 。苹果方面,早期 serif 为 「Times」,汉字会使用「华文宋体」,sans-serif 为「Helvetica」,汉字会 使用「华文黑体」(STHeiti),在 OSX 10.11 & IOS 9 之前推出过『冬青黑体』,之后中文则是 『苹方』。 Andorid 使用的就是 Google 自家的 『思源黑体』。
浏览器字体
对于西文字体,目前的 Chrome 在没有配置 generic family 的时候会选择 Arial,注意这是无衬线字体。
Chrome 会选择系统的默认中文字体,即 Windows 上『微软雅黑』,OSX 上『苹方』
Safari 的默认中文字体是 『苹方』,西文字体 是 『Times New Roman』
早期 GBK 编码下,Window 和 IE 会错误选择中文默认字体。现在绝大多数网站都会选用 UTF-8 编码并指定 lang 为 zh-CN。在指定了 lang 为 zh-CN后 Safari 对中西文的默认字体都会指向『苹方』
Android 上的浏览器则比较混乱,默认西文字体是 『Roboto』,中文字体『思源黑体』,但也会有厂商自定义默认字体,同时即便采用 UTF-8 编码并指定 lang 为 zh-CN,默认西文字体也可能会因为厂商定制而不一样。
自定义 Web font
思源 Pan-CJK 系列
思源系列字体,是Adobe 和 Google 合作开发,对日文、韩文、繁体中文和简体中文,7 种粗细类型都做了支持优化,不但免费而且开源的 OpenType 字体和源文件,我们都可以基于源码修改后个人或商业使用。
Pan-CJK 系列最早 在2014 年 首发,The Typekit Blog | 隆重介绍 思源黑体:一款Pan-CJK 开源字体
Adobe 称为「 思源黑体 」(Source Han Sans,源ノ角ゴシック、본고딕),Google font 中的名称是 Noto
2017 年发布了 思源宋体 (Source Han Serif,源ノ明朝、본명조)。
顾名思义,思源黑体和思源宋体分别属于无衬线和衬线字体。
个人字体选型
使用三套字体
对于个人网站,还是需要显式声明 font-family
,尽量避免让系统和浏览器选择字体。
通常我把西文字体放在中文前,先别让中文字体渲染英文字符,再让中文字符 fallback 到后面我期望的中文字体。同时还要考虑下系统版本,最后再列出 generic family 兜底。
另一方面,我希望区分衬线,对与技术类文章使用无衬线 sans serif 字体,对不太硬核的文章(尤其中文)使用衬线 serif 字体。
在首页、介绍等页面上,内容以无衬线字体方便快速阅读,但对标题用衬线字体强调。
最后,对于代码块,在 HTML 中的 pre、code 标签就比较适合使用等宽字体。
因此我定义了三套 font-family
:
字体加载处理
Google Font 托管最佳实践
之前一直用 Google Font 提供的字体托管服务,那么也就免不了要对加载速度做一系列优化。
相关最佳实践包括四步:
- 预连接字体源,给字体源预热
- 预加载字体 css,告诉浏览器作为字体下载,调整优先级
- 异步加载字体 css 兜底,浏览器会以低优先级处理异步 css 请求,从而不阻塞页面解析,并且仅应用于 print media
- noscript 再兜底用户禁用 js 的场景
同时,Google Font 生成的字体样式 url 现在也都会带上 font-display
属性。
Web Font 生命周期
这其实涉及到 Web Font 的生命周期:
- block 期,浏览器以不可见字体,渲染文本。
- swap 期,浏览器以 fallback 字体渲染文本。
- failure 期,浏览器找不到目标字体,永远以 fallback 字体渲染。
浏览器默认会采用 font-display: auto
, 实际效果类似 font-display: block
的方式渲染,在目标字体加载完成前先渲染成不可见文本,在目标字体加载完成后立即切换过去。所以表现出的效果就是在字体加载完成之前不显示,这也是引入三方字体很容易导致白屏 FOIT(Flash of Invisible Text) 瞬时不可见文本的重要原因之一。
如果配置成 font-display: fallback
,浏览器会先进入 block 期渲染不可见文本,这个 block 期很短(表现出短暂的页面白屏停顿,即所谓 FOUT(Flash of Unstyled Text) 瞬时未样式化文本效果);block 期过后进入 swap 期,如果目标字体还没加载完成,尝试用 fallback 字体渲染,一但目标字体加载,切换过去;swap 期很短,如果长时间目标字体加载未果,就会一直使用 fallback 字体。
如果配置成 font-display: swap
,浏览器会跳过 block 期(但还是会有微小的等待,接近 0 秒),如果目标字体还没加载完成,直接用 fallback 字体渲染,并且 swap 期无限长,一直等待到目标字体加载,切换过去。
Google Font 生成的字体样式 url 都会设成 font-display: swap
,个人认为也是比较好的实践方式。
但!
我最后还是放弃了 Google Font。一方面是因为 Google Font 会把中文字体拆成多个『字体切片』来保证对页面的按需加载。比如我的网站使用的字体会是 17 个 .woff2 文件请求,总体积在 700KB 左右,但这些字体即便不做按需加载总共也不过 1 MB,在如今的网络条件下这种优化效果意义并不大,而 17 个配置了 font-display: swap
的分散字体,还会出现页面因为陆续切换字体的内容错位效果;另一方面,无论如何优化加载速度,国内 gstatic.com
、 fonts.googleapis.com
两个域名被阻断的概率还是太高,连不上,更遑论加载优化。
Google Font 本地化
因此,我最终还是换成了本地化字体。Google WebFont Helper 支持我们按需配置并下载 WebFont 字体。
为了减少无用字符,我用的几种字体中,『思源宋体』(Noto Serif SC) 和『思源黑体』(Noto Sans SC) 只选择简体中文字符,其余只选择拉丁字符。
而在 @font-face css
方面,我同样加上了 font-display: swap
, 由于我只考虑支持现代浏览器,所以只选择了 woff
和 woff2
格式。
- woff(web open font format), Mozilla 联合各类组织专为 web 设计,对字体做了压缩,相比 tff 格式体积大减,加载速度更快。
- woff2, woff 的下个版本,有更好的压缩率。
注意我还设置了本地字体资源 local()
,由于字体文件通常不小,这可以告诉浏览器优先寻找本地机器上的字体资源。
配合字体加载 css :
其他字体格式也备注下,想要各种浏览器都支持 web font 基本需要把他们都配上:
- svg,适量字体,legency ios(ios 4 之前) safar 唯一支持的字体格式,占内存少。
- ttf(TrueType Font),前面提过,OSX 和 Windows 上最常见的字体,但没有压缩,资源文件较大。
- otf(OpenType Font),Windows 和 IE 常用,没有压缩,资源文件较大。
- eot(Embedded OpenType),微软开发,早期 IE 独有字体格式。
CDN 加速托管本地化字体
尽管我把字体本地化了,但如果完全 self-host 还是开销太大,依然还需要通过 cdn 加速。我最终选择的是 360 奇舞团维护的静态资源 75 CDN,它竟然令人惊喜的提供了 Google Font 本地化,输入 https://fonts.googleapis.com/css2?family=Merriweather:300
,
会直接返回相应的本地化资源 :
不过遗憾的是一些新字体这个服务还没同步,比如 『思源宋体-简体中文』(Noto Serif SC),所以我前面通过 [Google WebFont Helper 下载字体的操作也不算白忙活,对『思源宋体-简体中文』(Noto Serif SC) 依然自行托管。
总结
最后总结下我的实践:
- 区分衬线,技术类文章使用无衬线 sans serif 字体,不太硬核的文章使用衬线 serif 字体,改善中文阅读体验。
- 在首页、介绍等页面上以无衬线字体方便快速阅读,但对标题用衬线字体修饰。
- 对于代码块用等宽字体。
- 字体资源尽量本地化并 CDN 加速。
- 设置
font-display: swap
优化加载。
引用