# 基础介绍

# font-face 是什么

font-face 是 css3 中允许使用自定义字体的一个模块,他主要是把自己定义的 Web 字体嵌入到你的网页中。
这是一个叫做 @font-face 的 CSS @规则 , 它允许网页开发者为其网页指定在线字体。通过这种作者自备字体的方式,@font-face 可以消除对用户电脑客户端的字体的依赖

# @font-face 基本语法

@font-face {
  font-family: <font-name>;
  src: local( <family-name> ) | <url> [format("formatName")][,<url> [format("formatName")]]*;
  unicode-range: <unicode-range>;
  font-variant: <font-variant>;
  font-feature-settings: <font-feature-settings>;
  font-variation-settings: <font-variation-settings>;
  font-stretch: <font-stretch>;
  font-weight: <font-weight>;
  font-style: <font-style>;
  font-display: <font-display>;
}
@font-face {
 font-family: 'HYYaKuHei-85J';
 url('./fonts/HYYaKuHei-85J.ttf') format('truetype');
}
.box2 {
 font-family: 'HYYaKuHei-85J';
}

# url

表示服务器端提供的字体地址,这个也是可以使用多个,多个之间用逗号隔开,一般写多个是为了浏览器兼容加载不同格式的字体。目前 web 可以加载六种格式的字体:

字体格式有太多选择,不幸的是始终没有一个能在所有的浏览器上通用。这意味着,你 必须使用多种字体的方案来保持用户跨平台的一致性体验 。本文内容如题,会依次介绍一下 TTFOTFWOFFEOTSVG 几种字体目前在 Web 上的情况。

在这里插入图片描述

# TTF

TTF (TrueType Font) 字体格式是由苹果和微软为 PostScript 而开发的字体格式。在 Mac 和 Windows 操作系统上,TTF 一直是最常见的格式,所有主流浏览器都支持它。然而,IE8 不支持 TTF ;且 IE9 上只有被设置成 “installable” 才能支持。

TTF 允许嵌入最基本的数字版权管理标志 ———— 内置标志可以告诉我们字体作者是否允许改字体在 PDF 或者网站等处使用,所以可能会有版权问题。另一个缺点是,TTF 和 OTF 字体是没压缩的,因此他们文件更大。

# OTF

OTF (OpenType Font) 由 TTF 演化而来,是 Adobe 和微软共同努力的结果。

OTF 字体包含一部分屏幕和打印机字体数据。OTF 有几个独家功能,包括支持多平台和扩展字符集。OTF 字体可以在 Macintosh 和 Windows 系统上使用。

OTF 也允许多达 65000 个字符的存储。这个额外的空间让设计师可以自由地添加附加元素,比如小帽子、老式数字体、代替的字符和其他一些以前必须作为独立字体分发的附加材料。

(译注:苹果当年为了对抗 Adobe 在 PostScript 的 Type 1 字体拉上了微软一起撸了 TTF,结果后来微软又反水跟 Adobe 搞一套 OTF,还让 IE 后面的版本取消 TTF 支持.)

# EOT

EOT (Embedded Open Type) 字体是微软设计用来在 Web 上使用的字体 。是一个在网页上试图绕过 TTF 和 OTF 版权的方案。

你可以使用微软的工具从现有的 TTF/OTF 字体转成 EOT 字体使用,其中对字体进行压缩和裁剪使得文件体积更小。同时为了避免一些收版权保护的字体被随意复制,EOT 还集成了一些特性来阻止复制行为,以及对字体文件进行加密保护。听起来很有前途?嗯哼,可惜 EOT 格式只有 IE 支持。

# WOFF

WOFF (Web Open Font Format) 本质上是 metadata + 基于 SFNT 的字体(如 TTF、OTF 或其他开放字体格式)。

该格式完全是为了 Web 而创建,由 Mozilla 基金会、微软和 Opera 软件公司合作推出。 WOFF 字体均经过 WOFF 的编码工具压缩,文件大小一般比 TTF 小 40%,加载速度更快,可以更好的嵌入网页中 。metadata 允许在字体文件中包含许可数据,以解决版权问题。这是万维网联盟提(qing)倡(ding)的,所以毫无疑问的是字体格式的未来。目前主流的浏览器的新版本几乎都支持 WOFF。

WOFF2 是 WOFF 的下一代。 WOFF2 格式在原有的基础上提升了 30% 的压缩率。由于它还没有 WOFF 的广泛支持,所以还只是一个可展望的升级。

# SVG

SVG (Scalable Vector Graphics font) 字体格式使用 SVG 的字体元素定义。

这些字体包含作为标准 SVG 元素和属性的字形轮廓,就像它们是 SVG 映像中的单个矢量对象一样。

SVG 字体最大的缺点是缺少字体提示(font-hinting)。字体提示是渲染小字体时为了质量和清晰度额外嵌入的信息。同时,SVG 对文本(body text)支持并不是特别好。因为 SVG 的文本选择(text selection)目前在 Safari、Safari Mobile 和 Chrome 的一些版本上完全崩坏,所以你不能选择单个字符、单词或任何自定义选项,你只能选择整行或段落文本。

然而,如果你的目标是 iPhone 和 iPad 用户,需要说 SVG 字体是 iOS 上 Safari 4.1 以下唯一允许的字体格式。

# format

可选值,表示给加载的外部字体指定字体格式,用来告诉浏览器让浏览器能够识别所使用的字体格式,可用的类型有 embedded-opentype, truetype, opentype, woff, woff2, svg 。分别对应上边我们介绍的字体格式。

# @font-face 用法示例

/* 加载一种字体格式 */
@font-face{
  font-family: "myFontName";
  src:  url('font.woff') format('woff');
}
/* 加载多个字体格式,兼容更多浏览器 */
@font-face{
  font-family: "myFontName";
  src: url('font.eot'); /* IE9*/
  src: url('font.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
  url('font.woff2') format('woff2'),
  url('font.woff') format('woff'), /* chrome、firefox */
  url('font.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
  url('font.svg#Alibaba-PuHuiTi-Regular') format('svg'); /* iOS 4.1- */
}
  • font 字体下载 https://www.wfonts.com/font/simsun
  • ttf-to-eot 字体转换器 https://cloudconvert.com/ttf-to-eot

参考文献:https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face

# 详细解析 + 性能分析

最近一个项目需要接入新的字体,该字体总共大小 700k 左右(每一种字重 100kb 左右),实际上一个页面一般只会用到 2 到 3 重字体,浏览器只会去下载页面用到字重的字体,但就算如此,一个字体也有 100kb 左右,字体加载太快的时候,可能对页面的性能和体验不会有什么变化,但如果刚好使用的字体很大或者用户的网络较差的情况下,那么也将根据页面字体载入的方式来以不同程度影响用户的体验。

以下通过以下步骤来探索自定义字体性能优化的过程。

  • 了解 CSS 自定义字体(常用属性详解)
  • 了解可变字体
  • 自定义字体使用过程中会遇到的问题和优化方向
  • Next.js 项目中自定义字体最佳实践

# 了解 CSS 自定义字体

自定义字体就是指使用 CSS 的 @font-face 来定义字体类型,如果我们要引入自定义字体,我们可以去 Google Fonts 下载需要的字体,这里我下载了一个 Alegreya 字体,然后使用 @font-face 进行注册字体:

@font-face {
  font-family: "Alegreya";
  src: local("Alegreya"),
    url("./fonts/Alegreya/static/Alegreya-Regular.ttf") format("truetype");
  font-weight: 400;
}
@font-face {
  font-family: "Alegreya";
  src: local("Alegreya"),
    url("./fonts/Alegreya/static/Alegreya-Medium.ttf") format("truetype");
  font-weight: 500;
}
@font-face {
  font-family: "Alegreya";
  src: local("Alegreya"),
    url("./fonts/Alegreya/static/Alegreya-SemiBold.ttf") format("truetype");
  font-weight: 600;
}
@font-face {
  font-family: "Alegreya";
  src: local("Alegreya"),
    url("./fonts/Alegreya/static/Alegreya-Bold.ttf") format("truetype");
  font-weight: 700;
}

@font-face 中一般常用的其实也就 5 个属性:

  • font-family 字体家族名称
  • src 字体文件来源
  • font-display 字体载入页面方式
  • font-weight 定义什么字重的时候使用该字体
  • font-style 定义什么字重的时候使用该字体,和 font-weight 基本一致,但用到相对比较少一些,如果需要参考 font-weight 即可。

注意 @font-face 中的 font-familyfont-weightfont-style 三个属性和 css 常规样式中这三个属性是不一样的,只是有一个对应关系。

还有一些其他辅助属性,这里只简单的说明:

  • line-gap-override 定义字体的行间距度量,和 line-height 效果类似
  • ascent-override 定义字体的上升度量,和 padding-top 效果类似。
  • descent-override 定义字体的下降度量,和 padding-bottom 效果类似。
  • unicode-range 定义字体中要使用的 Unicode 代码点范围。具体可以去看看 CSS unicode-range 特定字符使用 font-face 自定义字体
  • size-adjust 提供字体缩放功能。和 transform 中的 scale 类似,但可以更精细的只控制文字。

上面的属性都是只能在 @font-face 中使用,下面两个熟悉不是用在 @font-face 中,而是具体 css 样式中:

  • font-stretch 和 size-adjust 相似,但兼容性更好,但只有特定字体才支持。
  • font-variation-settings 通过指定要变化的特征的四个字母轴名称及其变化值,允许对 OpenType 或 TrueType 字体变化进行低级控制。具体可以去看看 CSS font-feature-settings 50 + 关键字属性值完整介绍

下面来详细分析一下具体自定义字体中几个常用的属性的作用。

# font-family

@font-face 只是注册了字体,页面如果未进行使用,那么 Chrome 浏览器就不会去下载该字体文件,且如果使用到了该字体,未使用到的 weight 或者 style 也不会去下载对应样式的字体文件(Safari 浏览器只要注册就会去下载),则使用 font-family 指定字体家族名称:

.container {
    font-family: "Alegreya" Tahoma;
}

这里的 font-family 对应 @font-face 中的注册的 font-family ,如果没有自定义的字体,则使用 Tahoma 备用字体, Tahoma 也不存在,则使用系统默认字体。

# src

src 定义了字体文件来源, src 中有几个需要注意的方法:

  • local 指定本地系统字体

  • url 指定文件地址,本地地址和网络地址均可,跟图片链接一样。

  • format 主要是用来帮助浏览器识别字体类型

    • truetype —— .ttf
    • opentype —— .ttf 或者 .otf
    • woff —— .woff
    • woff2 —— .woff2

使用示例:

@font-face {
  src: local("Alegreya"),
    url("./fonts/Alegreya/static/Alegreya-Bold.ttf") format("truetype");
}

上面的示例的意思是指,优先查找本地系统中是否存在 Alegreya 字体,没有再去加载字体文件。

format 的作用并不是很大,现代浏览器不使用基本上也不会有问题,就算修改了字体文件的后缀名,且不使用 format 指定字体文件类型,也能正常加载并使用。

local 的作用很大,可以用于优化性能和简化代码写法。

# font-display

font-display 用于设置字体载入页面的方式,这里的 “载入”,指的是字体下载再合成到页面内容上去的过程。

“载入” 分为两个过程:下载、显示。

字体下载本身是一个高优先级的异步下载的过程,也就是下载过程本身并不阻塞浏览器的渲染(不算上网络请求竞争阻塞的情况)。

字体显示则是让浏览器读取到新的字体,然后使用新的字体去渲染页面内容。

这两个过程对应着页面呈现文字内容的三个时期:

  • font block period 字体块期,如果自定义字体未下载完成,那使用该字体的元素则显示 不可见 的后备字体。在此期间,如果字体下载完成,则显示自定义字体。
  • font swap period 字体交换期,如果自定义字体还没下载完成,则使用备用字体,如果在此期间自定义字体成功加载,则使用自定义字体。
  • font failure period 字体失效期,如果自定义字体还没下载完成,则放弃使用自定义字体。

显示自定义字体的条件是必须等字体下载完成才行,因此如果没有备用方案,那么字体过大可能就很影响页面显示的时间。 font-display 的几个属性值就是用于控制自定义字体的 “载入” 在 “三个时期” 中的处理方式:

image.png

具体分析:

  • block 等自定义字体下载完成后再显示页面内容,3s 后字体还没下载完毕,则显示备用字体(系统字体),等字体下载完后再替换页面字体。

问题:字体会直接阻塞页面显示,延长页面显示时间,如果超过 3s 才显示页面内容,那么字体交换也会导致页面抖动。适合页面性能较好、网速较快的情况。

  • swap 直接使用备用字体来显示页面内容,等自定义字体下载完后显示自定义字体。页面会出现抖动。

问题:字体切换交换时也很可能会导致页面抖动,网速越快抖动效果越不明显。适合需要快速显示页面内容,能容忍字体导致页面轻微抖动的情况。

  • fallback 等 100ms 毫秒再显示页面内容(这段时间白屏),这时候这段时间内字体字体下载完成,则使用自定义字体,否则使用备用字体来显示页面内容,等自定义字体下载完后再显示自定义字体。

问题:除非 100ms 内下载完成了字体,否则页面会出现抖动。适合字体文件小,网速慢的时候可以接受字体导致页面轻微抖动的情况。

  • optional 等 100ms 毫秒在显示页面内容(这段时间白屏),这时候这段时间内字体字体下载完成,则使用自定义字体,否则使用备用字体来显示页面内容,如果超过 100ms 后自定义字体才下载完,也不进行交互字体。

问题:页面性能会差一点,但 100ms 影响较小,且首次也会去下载字体,第二次访问的时候字体有缓存就可以读取缓存,会直接显示自定义字体。适合字体文件小,字体文件大的情况能容忍首次访问页面不显示自定义字体的情况。

  • auto 默认值,由浏览器决定,一般默认的表现和 block 类似。

# 了解可变字体

可变字体(Variable fonts)是 OpenType 字体规范上的演进,它允许将同一字体的多个变体统合进单独的字体文件中。从而无需再将不同字宽、字重或不同样式的字体分割成不同的字体文件。你只需通过 CSS 与一行 @font-face 引用,即可获取包含在这个单一文件中的各种字体变体。

这是来自于 MDN 可变字体

也就是说我们可以不再需要根据不同字重等因素去单独引入多个字体文件。只需要直接使用一个字体文件即可,这一个字体文件一般情况下是远远小于多个字体文件子集的和,就以 Alegreya 字体来说,它的可变字体文件大小为 270kb ,但是它的一个单个字重的字体文件就有 163kb 。因此可大大减少字体文件的大小,且可以实现更多更全面的样式。去 Google Fonts 下载的时候也会一并下载下来。

image.png

接入就不需要写那么多 @font-size 了,这里有两个可变字体文件,一个是:

// 正常可变字体
@font-face {
  font-family: "Alegreya";
  src: local("Alegreya"),
    url("./fonts/Alegreya/Alegreya-VariableFont_wght.ttf") format("truetype");
}
// 斜体,不需要斜体的不引入即可
@font-face {
  font-family: "Alegreya";
  src: local("Alegreya"),
    url("./fonts/Alegreya/Alegreya-Italic-VariableFont_wght.ttf") format("truetype");
  font-style: italic; // 制定斜体的时候使用该字体文件
}

可变字体可以通过 font-stretchfont-variation-settings 来进行控制,只要字体文件支持,就可以对字体样式进行最细粒度的样式调整,对于需要更多风格的字体样式来说,是一个相当不错的方式。不过 font-variation-settings 的兼容性很差。

image.png

但可变字体库的兼容性不受此属性的的影响,兼容性还是挺好。

image.png

关于可变字体的具体介绍,推荐一下 web dev 上的一篇文章:Introduction to variable fonts on the web

# 自定义字体使用过程中会遇到的问题和优化方向

每一种字体少则几百 kb ,多则几 M,接入自定义字体多少都会对页面的性能和体验造成一定的影响。最好的方式当然是使用系统自带的字体,但有时候设计师或者产品经理就是要加入新的字体,因此面对这种情况很有必要去了解如何去自定义字体,以及如何减少自定义字体对页面性能和体验的影响。

字体文件影响页面性能和体验的最大原因就是因为它们的体积造成的,因此优化体积是一个关键点,减少字体的方式有两种方向:

  • 去除不需要的字体文件的内容
  • 在需要使用多个字重、斜体或者更多字体样式控制的情况下考虑使用可变字体

除了优化体积,其他最需要注重的还有两点:

  1. 避免加载系统已有的字体,比如如果是做 webview 页面,那么一般 web 页面的字体需要保持和原生 app 内一直,如果原生 app 中注册了字体,那么 web 页面就不需要远程下载字体,这时候使用 local 将是一个很好的处理方式。因此,始终使用 local 作为本地字体探针也是一个很好的优化方式。
  2. 选择合适的字体载入页面的方式,字体的 font-display 属性设置也会影响性能和体验,因此可以针对不同的需求,选择使用合适的属性。

# Next.js 项目中自定义字体最佳实践

Next.js 中本身提供了对 font 的优化方式:

  • 去除不需要的字体文件的内容:指定字体文件的子集,一般字体会兼容全球的字符集,但是很多时候我们不需要显示某些字符集,一般只需要 latin 字符集(拉丁字符集)即可(内部移除不需要的字符集,减少字体体积)。
  • 根据字重 / 是否需要斜体等情况来优化字体包大小
  • 使用 link preload 进行提前下载字体
  • 使用 gzip 压缩字体文件

下面将以接入 Alegreya 字体为例:

@font-face {
  font-family: "Alegreya";
  src: local("Alegreya") format("truetype");
}

此处不进行远程加载字体文件,只读取本地字体,用于当系统字体中存在 Alegreya 时,不进行远程下载。

使用 next/font 接入 Alegreya 字体:

import { Alegreya } from "next/font/google";
const alegreya = Alegreya({
  subsets: ["latin"], // 设置需要的字符集
  weight: ["400", "500", "600", "700"], // 设置需要的字重
  style: ["normal", "italic"], // 有 normal 和 italic 可选择
})
function App(props) {
  return (
    <>
      {/* 有本地字体库使用本地字体库,没有则使用 next/font/google 中的字体库 */}
      <style jsx global>{`
        body {
          font-family: "Alegreya", ${Alegreya.style.fontFamily};
        }
        // 表单相关元素 font-family 会被浏览器给设置为空,而不会继承 body
        input,
        button,
        textarea {
          font-family: inherit;
        }
      `}</style>
      <Component {...props.pageProps} />
    </>
  );
}

Alegreya.style.fontFamily 并不是 “Alegreya” 字符串,而是 __Alegreya_Fallback_xxxx 这样一个字符串,这样就可以不影响系统可能存在的字体。

使用 next/font 后, Next.js 会自动根据传递的配置进行优化字体,默认也会压缩字体文件。我们看一下优化后的字体文件大小对比:

image.png

字体原文件大小:

image.png

可以看出 gzip 压缩后字体会缩小将近一倍,Next.js 剔除不需要的字体内容后,文件小了好几倍,如果只需要一个字重的情况,字体文件还会小更多。

修改配置为:

const alegreya = Alegreya({
  subsets: ["latin"], // 设置需要的字符集
  weight: ["400"], // 设置需要的字重
  style: ["normal", "italic"], // 有 normal 和 italic 可选择
})

文件大小数据:

image.png

多个字重和单个字重会采用不同的字体方案,大于等于 2 个字重才会使用可变字体,因此文件的数量会根据子重和是否需要斜体的情况有所变化。

参考文献:

  • Web 性能优化:使用 CSS font-display 控制字体加载和替换
  • 真正了解 CSS3 背景下的 @font face 规则
  • MDN @font-face