Skip to content

Web Font

字体基础

  • 字符 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。

网页字体加载技术

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

  • 预连接字体源,给字体源预热
  • 预加载字体 css,告诉浏览器作为字体下载,调整优先级
  • 异步加载字体 css 兜底,浏览器会以低优先级处理异步 css 请求,从而不阻塞页面解析,并且仅应用于 print media
  • noscript 再兜底用户禁用 js 的场景
html
<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 也具有生命周期,具体:

  • 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 期无限长,一直等待到目标字体加载,切换过去。

字体分片

https://imqi1.com 中,网页使用的是 思源宋体,为加快字体访问速度,该网页使用了字体分片加载的技术,具体就是:

css
@font-face {
    font-family: "Noto Serif SC";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url(https://cdn.imqi1.com/static/notoserifsc/H4c8BXePl9DZ0Xe7gG9cyOj7mlK1SzUpCNMKEN0nmGnGv-OMEQDgKS-k5SiuioPhBdQcziZZTQ.4.woff2) format("woff2");
    unicode-range: U+1f1e9-1f1f5,U+1f1f7-1f1ff,U+1f21a,U+1f232,U+1f234-1f237,U+1f250-1f251,U+1f300,U+1f302-1f308,U+1f30a-1f311,U+1f315,U+1f319-1f320,U+1f324,U+1f327,U+1f32a,U+1f32c-1f32d,U+1f330-1f357,U+1f359-1f37e
}

@font-face {
    font-family: "Noto Serif SC";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url(https://cdn.imqi1.com/static/notoserifsc/H4c8BXePl9DZ0Xe7gG9cyOj7mlK1SzUpCNMKEN0nmGnGv-OMEQDgKS-k5SiuioPhBdQcziZZTQ.5.woff2) format("woff2");
    unicode-range: U+fee3,U+fef3,U+ff03-ff04,U+ff07,U+ff0a,U+ff17-ff19,U+ff1c-ff1d,U+ff20-ff3a,U+ff3c,U+ff3e-ff5b,U+ff5d,U+ff61-ff65,U+ff67-ff6a,U+ff6c,U+ff6f-ff78,U+ff7a-ff7d,U+ff80-ff84,U+ff86,U+ff89-ff8e,U+ff92,U+ff97-ff9b,U+ff9d-ff9f,U+ffe0-ffe4,U+ffe6,U+ffe9,U+ffeb,U+ffed,U+fffc,U+1f004,U+1f170-1f171,U+1f192-1f195,U+1f198-1f19a,U+1f1e6-1f1e8
}

从上面的代码中,可以看到,整体的字体文件被分割成数十个字体小文件,每个小文件只包含特定范围下的字符。当网页上出现了在此范围的字符时,就可以只加载那部分的字体文件,从而无需加载整个字体文件。

这里用到了一个小工具,名称为 cn-font-split,它是开源的,项目地址为 https://github.com/KonghaYao/cn-font-split ,可使用 npm install cn-font-split -g 安装。

使用也很简单。

shell
# -i 输入 -o 输出
cn-font-split -i=../demo/public/SmileySans-Oblique.ttf -o=./temp

# 参数与正常 js 操作是一样的,深层json则需要使用 . 来赋值
cn-font-split -i=../demo/public/SmileySans-Oblique.ttf -o=./temp --renameOutputFont='[hash:10][ext]' --css.fontWeight=700

# 显示输入参数说明,虽然会显示 typescript 类型。
cn-font-split -h

这里也提供一些我觉得好看,以后可能用到的字体,更多分片式字体文件请前往 Google Font,但字体使用方法和本教程相同,只是地址换了。Google Font 提供的 CDN 国内可以放心使用,速度很快。

ROBOTO

使用:

html
<link href="https://static.qi1.website/css/font-roboto.css" rel="stylesheet">

<style>
    .roboto-font {
        font-family: "Roboto", sans-serif;  /* font-family 值选 Roboto */
        font-weight: 300;  /* font-weight 值可选:100、300、400、500、700、900 */
        font-style: italic;  /* font-style 值可选:normal、italic */
    }
</style>

JetBrains Mono

适用于代码展示,官网为 https://www.jetbrains.com/lp/mono/

使用:

html
<link href="https://static.qi1.website/css/font-jetbrainsmono.css" rel="stylesheet">

<style>
    .jetbrains-mono-font {
        font-family: "JetBrains Mono", sans-serif;  /* font-family 值选 JetBrains Mono */
        font-weight: 300;  /* font-weight 值可选:300、500、700 */
        font-style: italic;  /* font-style 值可选:normal、italic */
    }
</style>

Inria Sans

适用于某些艺术字展示,官网为 https://fonts.google.com/specimen/Inria+Sans

html
<link href="https://static.qi1.website/css/font-inriasans.css" rel="stylesheet">

<style>
  .inria-sans-light {
  font-family: "Inria Sans", sans-serif;
  font-weight: 300;    /* font-weight 值可选:300、400、700 */
  font-style: normal;  /* font-style 值可选:normal、italic */
}
</style>

思源黑体

思源黑体我认为比微软雅黑更好看,和鸿蒙字体并列。

使用:

html
<link href="https://static.qi1.website/css/font-notosanssc.css" rel="stylesheet">

<style>
    .noto-sans-sc-<uniquifier> {
        font-family: "Noto Sans SC", sans-serif;  /* font-family 值选 Noto Sans SC */
        font-optical-sizing: auto;
        font-weight: <weight>;  /* font-weight 值可选:100 ~ 900 */
        font-style: normal;
    }
</style>

思源宋体

思源宋体我觉得是思源系列最好看的字体了,我的主站点 imqi1.com 目前使用的也是此字体。

使用:

html
<link href="https://static.qi1.website/css/font-notoserifsc.css" rel="stylesheet">

<style>
    .noto-serif-sc-<uniquifier> {
            font-family: "Noto Serif SC", serif;  /* font-family 值选 Noto Serif SC */
            font-optical-sizing: auto;
            font-weight: <weight>;  /* font-weight 可选值有 200 ~ 900 */
            font-style: normal;
        }
</style>

苹果字体

由于苹果字体没有官方的 CDN,也没有分片,所以我自己制作了两款字重的苹果字体,分别为普通和粗体。

使用:

html
<link href="https://static.qi1.website/css/font-pingfangsc.css" rel="stylesheet">

<style>
    .pingfang-sc-<uniquifier> {
            font-family: "PingFang SC", serif; 
            font-optical-sizing: auto;
            font-weight: <weight>; /* font-weight 可选值有 400 和 700 */
            font-style: normal;
        }
</style>

为方便开发而创建的常用库指南