1. 从8MB到900KB:一次真实的性能优化经历

去年我接手了一个企业官网的改版项目,上线后测试环境反馈页面加载异常缓慢,特别是字体加载完成后页面会“闪”一下,用户体验很糟糕。排查后发现,罪魁祸首是两个阿里巴巴普惠字体包,每个都接近8MB。在本地开发时因为网络快感觉不明显,但一到测试和生产环境,用户需要等待近一分钟才能看到正确的字体渲染,这完全不可接受。

我当时就想,一个网站真的需要用到字体包里所有的汉字、符号和英文字母吗?显然不是。我们的页面内容相对固定,主要就是公司介绍、产品说明和联系方式,用到的字符数量有限。如果能只提取页面实际用到的字符生成新的字体文件,体积不就能大幅减小了吗?这就是 font-spider(字蛛)的核心价值——它不是一个简单的压缩工具,而是一个智能的WebFont按需裁剪器。

简单来说,它的工作原理就像“字体雕刻师”。原始的 .ttf 字体文件好比一块完整的玉石,包含了该字体设计的所有字符(通常是成千上万个)。而我们的网页设计稿,只需要其中特定的几百个字符来“雕刻”出最终的作品。font-spider 的工作就是分析你的HTML和CSS,找出你到底用了哪些字(比如“公司简介”、“科技创新”这些词),然后从完整的字体玉石上,精准地“切”下这些字符,生成一个全新的、只包含所需字符的微型字体文件。你用的字越少,生成的文件就越小。在我那个案例里,通过纳入约3500个常用汉字和基础符号,成功将8MB的字体包压缩到了900KB左右,加载时间从几十秒降到了几秒内,效果立竿见影。

2. 核心工具 font-spider 的安装与快速上手

font-spider 是一个基于 Node.js 的命令行工具,安装和使用都非常简单。首先,你需要确保系统已经安装了 Node.js 环境(建议版本12以上)。打开你的终端或命令行工具,全局安装 font-spider:

npm install font-spider -g

安装完成后,你可以通过 font-spider --version 来验证是否安装成功。

接下来,你需要准备一个工作目录。这个目录里需要包含以下几样东西:

  1. 未经压缩的原始字体文件(例如 AlibabaPuHuiTi-2-35-Thin.ttf)。
  2. 一个定义了 @font-face 的 CSS 文件。
  3. 一个包含了所有需要使用该字体的文本的 HTML 文件。

我们先从 CSS 文件开始,通常我把它命名为 font.css:

/* font.css */
@font-face {
  font-family: 'AlibabaPuHuiTi-2-35-Thin';
  /* 指向原始的、未压缩的 .ttf 文件 */
  src: url('./AlibabaPuHuiTi-2-35-Thin.ttf');
  font-weight: normal;
  font-style: normal;
}

.thin {
  font-family: 'AlibabaPuHuiTi-2-35-Thin';
}

@font-face {
  font-family: 'AlibabaPuHuiTi-2-45-Light';
  src: url('./AlibabaPuHuiTi-2-45-Light.ttf');
  font-weight: normal;
  font-style: normal;
}

.light {
  font-family: 'AlibabaPuHuiTi-2-45-Light';
}

这里的关键是 src 的路径必须指向原始的 .ttf 文件,因为 font-spider 主要就是分析这个格式。

然后,创建核心的 HTML 文件(比如 index.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>字体提取模板</title>
    <link rel="stylesheet" href="./font.css">
</head>
<body>
    <!-- 这个div中的文字,将会被提取并生成“Thin”字重的压缩字体 -->
    <div class="thin">
        这里是需要应用“阿里巴巴普惠体2.0 35 Thin”字体的所有文字。
        例如:公司名称、导航菜单、核心标语等。
        你可以把整个网站所有会用到的、需要此字体的中文、英文、数字、符号都写在这里。
    </div>
    
    <!-- 这个div中的文字,将会被提取并生成“Light”字重的压缩字体 -->
    <div class="light">
        这里是需要应用“阿里巴巴普惠体2.0 45 Light”字体的所有文字。
        注意:两个字体文件是独立的,需要分别列出其用字。
    </div>
</body>
</html>

这个 HTML 文件是 font-spider 的分析依据,至关重要。 它就像一份“采购清单”,你写在 <div class=”thin”> 和 <div class=”light”> 里的所有字符,就是 font-spider 要从原始字体中“采购”并打包的内容。清单之外的字符,在新的字体包里将不复存在,渲染时会回退到系统默认字体。

最后,在终端中进入这个工作目录,执行压缩命令:

font-spider index.html

命令执行成功后,你会看到原始的 .ttf 文件被自动替换为裁剪后的新文件,同时原文件被备份到了新生成的 .font-spider 文件夹里。你的 font.css 文件无需任何修改,因为它引用的文件路径没变,但背后的字体文件已经“瘦身”成功了。

2.1 操作中的常见“坑”与解决方案

在实际操作中,你可能会遇到一些问题,这里我总结几个常见的:

  • 路径问题:font-spider 分析时依赖相对路径。如果 CSS 或 HTML 中字体文件的路径不对,会报 Web Font was not found 错误。确保在 font.css 中使用的 url(‘./font.ttf’) 能正确指向文件。
  • 格式依赖:虽然我们最终可能使用 .woff2 格式以获取更好压缩率,但 font-spider 主要处理 .ttf 格式。请确保 @font-face 中 src 的第一个或主要声明指向的是 .ttf 文件。
  • 动态内容失效:这是 font-spider 最大的局限性。它只对压缩时 HTML 文件中静态存在的文字有效。如果文字是通过 JavaScript 动态加载、用户输入或从接口获取的,这些新字符将无法显示为特殊字体。因此,它最适合文章、官网、宣传页等文字内容固定的场景。

3. 多框架项目(Vue/React)中的集成方案

在现代前端项目中,我们很少直接写静态HTML。在 Vue 或 React 项目中集成 font-spider,核心思想是一样的:创建一个用于提取字体的“模板文件”,这个文件集中了项目中所有可能用到该字体的文本。

3.1 Vue 项目中的实践

假设你的 Vue 项目使用单文件组件。你可以在 public 目录下(Vue CLI 创建的项目)或项目根目录创建一个专门的字体提取目录,比如 font-extract/。

  1. 创建提取模板:在 font-extract/ 下创建 index.html 和 font.css,其内容与上文基础示例一致。
  2. 收集项目用字:你需要手动或通过一些辅助脚本,遍历你的 .vue 文件、.js 文件,将所有使用到该特殊字体的文本字符串提取出来,汇总到 index.html 的对应 <div> 中。这是一个需要耐心但一次性的工作。例如,你可能有:
    <div class="thin">
        {{ 从Home.vue提取的标题 }} {{ 从About.vue提取的段落 }} 
        {{ 从Product.vue提取的产品名 }} 登录注册提交取消 <!-- 按钮文字 -->
    </div>
    
  3. 执行压缩:在 font-extract/ 目录下运行 font-spider index.html。
  4. 集成字体文件:将生成的、压缩后的 .ttf 文件(或转换后的 .woff2 文件,见下文)放入项目的静态资源目录,如 src/assets/fonts/,并在项目的全局样式(如 src/style.css 或 main.js 中引入的CSS)中更新 @font-face 的引用路径。

3.2 React 项目中的实践

React 项目的思路与 Vue 类似。

  1. 建立提取环境:在项目根目录新建 font-extract/ 文件夹,放置 index.html 和 font.css。
  2. 聚合文本内容:检查所有 jsx、tsx 文件以及字符串常量文件,找出所有用到了 font-family: ‘YourFont’ 的文本。将这些文本整理到 index.html 里。如果项目使用了国际化(i18n),你需要包含所有语言包中对应的键值。
  3. 运行与替换:执行压缩命令后,将得到的新字体文件移入 public/fonts/ 或 src/assets/fonts/,并更新全局样式中的 @font-face 定义。

关键提示:对于大型项目,手动收集文字容易遗漏。可以考虑写一个简单的 Node 脚本,使用 fs 模块读取项目文件,通过正则表达式匹配特定 CSS 类名或字体族名下的文本内容,自动生成 index.html。虽然有点工作量,但一劳永逸。

4. 高级技巧:高频汉字预置与字体格式优化

仅仅压缩还不够,我们还要追求极致。这里分享两个进阶技巧。

4.1 构建你的“高频汉字库”

在准备那个关键的 index.html 文件时,除了写入项目本身的文案,我强烈建议你预置一个高频汉字和字符集。这样即使未来有小幅文案调整,只要不超出这个字库,就无需重新压缩字体。我常用的一个基础集包括:

  • 约2500-3500个常用汉字:覆盖绝大多数书面语。
  • 大小写英文字母(A-Z, a-z)。
  • 数字0-9。
  • 常用标点符号:,。!?、;:“”‘’()【】《》…—~·¥$等。
  • 网页常用字符:@#%&*+-=/<>等。

你可以从网上的“现代汉语常用字表”获取这些汉字,整理成一个文本文件,然后在构建提取模板时引入。这样生成的字体文件可能在900KB-1.5MB左右,但能覆盖绝大多数场景,比全量字体小得多,且非常稳妥。

4.2 拥抱更优的 WOFF2 格式

font-spider 默认输出的是 .ttf 文件。但现代网页性能优化推荐使用 .woff2 格式,它比 .ttf 和 .woff 拥有更高的压缩率(通常再减少20%-30%)。我们可以将 font-spider 产出的 .ttf 进一步转换。

首先,安装转换工具 ttf2woff2:

npm install -g ttf2woff2

然后,对压缩后的 .ttf 文件进行转换:

# 在字体文件所在目录执行
cat AlibabaPuHuiTi-2-35-Thin.ttf | ttf2woff2 > AlibabaPuHuiTi-2-35-Thin.woff2
cat AlibabaPuHuiTi-2-45-Light.ttf | ttf2woff2 > AlibabaPuHuiTi-2-45-Light.woff2

(Windows用户可在Git Bash或WSL中运行,或使用其他图形化转换工具。)

最后,更新你的 font.css,优先使用 woff2 格式,并做好兼容性回退:

@font-face {
  font-family: 'AlibabaPuHuiTi-2-35-Thin';
  src: url('./AlibabaPuHuiTi-2-35-Thin.woff2') format('woff2'),
       url('./AlibabaPuHuiTi-2-35-Thin.ttf') format('truetype'); /* 备用 */
  font-weight: normal;
  font-style: normal;
  font-display: swap; /* 关键:优化加载行为 */
}

这里引入了 font-display: swap; 这个重要的CSS属性。它告诉浏览器:先用备用字体立即显示文字,等自定义字体下载完成后再交换过来。这可以有效避免页面加载初期因等待字体而出现的文字空白期(FOIT),提升感知性能。

5. 生产环境部署与加载策略

字体文件压缩好后,在生产环境的部署和加载策略上也有些讲究。

首先,别忘了开启服务器压缩(如GZIP或Brotli)。虽然 woff2 本身已压缩,但服务器层压缩对 ttf 等格式仍有增益。同时,为字体文件设置长期的缓存策略,利用 Cache-Control 响应头(例如 public, max-age=31536000, immutable),让用户浏览器缓存字体,第二次访问时直接使用本地缓存。

其次,考虑使用 <link rel=”preload”>。对于首屏关键内容使用的字体,可以在HTML的 <head> 中加入预加载提示,让浏览器更早地发现并下载字体资源,缩短渲染阻塞时间。

<head>
  <link rel="preload" 
        href="/assets/fonts/AlibabaPuHuiTi-2-35-Thin.woff2" 
        as="font" 
        type="font/woff2" 
        crossorigin="anonymous">
</head>

最后,做好字体加载失败的优雅降级。在 font-family 声明中,始终提供可靠的后备字体栈。

body {
  font-family: 'AlibabaPuHuiTi-2-35-Thin', 
               -apple-system, 
               BlinkMacSystemFont, 
               'Segoe UI', 
               'PingFang SC', 
               'Microsoft YaHei', 
               sans-serif;
}

这样即使自定义字体因网络问题加载缓慢或失败,用户也能立刻看到清晰可读的系统字体,体验不会被打断。

经过这一套组合拳——从8MB全量字体,到按需裁剪至900KB,再到转换为WOFF2格式,并配合合理的加载策略——你的网页字体加载性能将得到质的飞跃。我实测下来,在常规网络环境下,字体加载从原来的数十秒优化到1-2秒内完成,页面不再有突兀的字体闪烁,核心内容得以即刻呈现。对于前端性能优化而言,字体瘦身是一个投入产出比极高的环节,值得每一位开发者掌握。

更多推荐