惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
有赞技术团队
有赞技术团队
Jina AI
Jina AI
H
Help Net Security
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Blog — PlanetScale
Blog — PlanetScale
Hugging Face - Blog
Hugging Face - Blog
罗磊的独立博客
MyScale Blog
MyScale Blog
N
Netflix TechBlog - Medium
B
Blog RSS Feed
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
T
The Blog of Author Tim Ferriss
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
MongoDB | Blog
MongoDB | Blog
美团技术团队
M
MIT News - Artificial intelligence
阮一峰的网络日志
阮一峰的网络日志
博客园 - 司徒正美
Microsoft Security Blog
Microsoft Security Blog
IT之家
IT之家

ALBERTAZ

How I Built a Scannable 3D QR Code Generator | Every QR Code OpenStreetMap Data Pipeline for Canvas Maps | EveryCityMap 每日关灯:今天这题理论最少 8 步 OpenStreetMap + Canvas 城市地图实践:EveryCityMap 的五个工程取舍 OpenStreetMap + Canvas 城市地图实践:EveryCityMap 的五个工程取舍 博客 CDN 迁移到 Cloudflare R2 博客 CDN 迁移到 Cloudflare R2 Migrate my website to SvelteKit2 Migrate my website to SvelteKit2 漫谈编辑器 漫谈编辑器 Pure ESM package Pure ESM package 又折腾博客了 又折腾博客了 Rewrite my website with Svelte Rewrite my website with Svelte 字体漫谈-网站字体最佳实践 你不知道的 Web Components - 现状 你不知道的 Web Components - 现状 你不知道的 Web Components - 过去和未来 你不知道的 Web Components - 过去和未来 Node 应用本地 HTTPS Node 应用本地 HTTPS docker 部署 Gatsby 应用 docker 部署 Gatsby 应用 再提示『您的磁盘几乎已满』算我输 再提示『您的磁盘几乎已满』算我输 G6-Mobile 的前世今生 G6-Mobile 的前世今生
字体漫谈-网站字体最佳实践
https://www.albertaz.com/about · 2022-05-22 · via ALBERTAZ

字体基础

先理清几个概念

  • 字符 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 algorithmCSS Fonts Module Level 3CSS 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的兼容写法。

systemui-caniuse

由于无衬线字体更适合屏幕阅读,所以各个系统一般都会带有无衬线字体 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:

CSS
.serif() {
  font-family: 
    'Merriweather',
    'Noto Serif SC',
    serif;
}
.sans-serif() {
  font-family:
    system-ui,
    -apple-system,
    Arial,
    'Noto Serif SC',
    'Microsoft YaHei', 
    sans-serif;
}
.monospace() {
  font-family: 
    'Fira Code',
    system-ui,
    -apple-system,
    Arial,
    sans-serif;
}

字体加载处理

Google Font 托管最佳实践

之前一直用 Google Font 提供的字体托管服务,那么也就免不了要对加载速度做一系列优化。 相关最佳实践包括四步:

  • 预连接字体源,给字体源预热
  • 预加载字体 css,告诉浏览器作为字体下载,调整优先级
  • 异步加载字体 css 兜底,浏览器会以低优先级处理异步 css 请求,从而不阻塞页面解析,并且仅应用于 print media
  • noscript 再兜底用户禁用 js 的场景
HTML, XML
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Fira+Mono&family=Merriweather:wght@300;400;700&family=Noto+Sans+SC:wght@300;400;700&family=Noto+Serif+SC:wght@300;400;600;700&family=Open+Sans:wght@300;400;600;700&display=swap" as="font" crossorigin />
  <link href="https://fonts.googleapis.com/css2?family=Fira+Mono&family=Merriweather:wght@300;400;700&family=Noto+Sans+SC:wght@300;400;700&family=Noto+Serif+SC:wght@300;400;600;700&family=Open+Sans:wght@300;400;600;700&display=swap" rel="stylesheet"
      as="font" 
      crossorigin
      media="print" onload="this.media='all'" />
  <noscript>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fira+Mono&family=Merriweather:wght@300;400;700&family=Noto+Sans+SC:wght@300;400;700&family=Noto+Serif+SC:wght@300;400;600;700&family=Open+Sans:wght@300;400;600;700&display=swap" crossorigin />
  </noscript>

同时,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.comfonts.googleapis.com 两个域名被阻断的概率还是太高,连不上,更遑论加载优化。

Google Font 本地化

因此,我最终还是换成了本地化字体。Google WebFont Helper 支持我们按需配置并下载 WebFont 字体。

为了减少无用字符,我用的几种字体中,『思源宋体』(Noto Serif SC) 和『思源黑体』(Noto Sans SC) 只选择简体中文字符,其余只选择拉丁字符。

而在 @font-face css 方面,我同样加上了 font-display: swap, 由于我只考虑支持现代浏览器,所以只选择了 woffwoff2 格式。

  • woff(web open font format), Mozilla 联合各类组织专为 web 设计,对字体做了压缩,相比 tff 格式体积大减,加载速度更快。
  • woff2, woff 的下个版本,有更好的压缩率。
CSS
@font-face {
  font-family: 'Noto Serif SC';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: local('Noto Serif SC'),
       url('../fonts/noto-serif-sc-v22-chinese-simplified-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
       url('../fonts/noto-serif-sc-v22-chinese-simplified-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

注意我还设置了本地字体资源 local() ,由于字体文件通常不小,这可以告诉浏览器优先寻找本地机器上的字体资源。

配合字体加载 css :

HTML, XML
  <link rel="preload" as="font" type="font/woff2" href="/font/noto-sans-sc.css" >
  <link rel="stylesheet"  type="text/css" href="/font/noto-sans-sc.css"  media="print" onload="this.media='all'" />

其他字体格式也备注下,想要各种浏览器都支持 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 , 会直接返回相应的本地化资源 :

CSS
/* merriweather-300 */
@font-face {
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 300;
  src: url('//lib.baomitu.com/fonts/merriweather/merriweather-300.eot'); /* IE9 Compat Modes */
  src: local('Merriweather Light'), local('Merriweather'),
       url('//lib.baomitu.com/fonts/merriweather/merriweather-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('//lib.baomitu.com/fonts/merriweather/merriweather-300.woff2') format('woff2'), /* Super Modern Browsers */
       url('//lib.baomitu.com/fonts/merriweather/merriweather-300.woff') format('woff'), /* Modern Browsers */
       url('//lib.baomitu.com/fonts/merriweather/merriweather-300.ttf') format('truetype'), /* Safari, Android, iOS */
       url('//lib.baomitu.com/fonts/merriweather/merriweather-300.svg#Merriweather') format('svg'); /* Legacy iOS */
}
  

不过遗憾的是一些新字体这个服务还没同步,比如 『思源宋体-简体中文』(Noto Serif SC),所以我前面通过 [Google WebFont Helper 下载字体的操作也不算白忙活,对『思源宋体-简体中文』(Noto Serif SC) 依然自行托管。

总结

最后总结下我的实践:

  • 区分衬线,技术类文章使用无衬线 sans serif 字体,不太硬核的文章使用衬线 serif 字体,改善中文阅读体验。
  • 在首页、介绍等页面上以无衬线字体方便快速阅读,但对标题用衬线字体修饰。
  • 对于代码块用等宽字体。
  • 字体资源尽量本地化并 CDN 加速。
  • 设置 font-display: swap 优化加载。

引用