摘要:为了让游戏 UI 更具个性,我们使用了自定义的 Web Font(如 “Clear Sans”)。但在应用启动时,文字会先显示为系统默认的宋体,几百毫秒后才突然"跳变"成自定义字体。这种现象被称为 FOUT (Flash of Unstyled Text)。本文记录了如何通过预加载和 Base64 内联方案,彻底根治字体闪烁。

😵 1. 视觉违和感

现象

  1. 应用冷启动。
  2. Logo 下方的 “2048” 文字显示为细细的默认字体(Sans-serif)。
  3. 0.5秒后,文字突然变粗、变圆润(加载好了 Clear Sans)。
  4. 布局发生微小的位移(抖动)。

这种体验非常廉价,仿佛应用还没准备好就匆忙见客了。

📉 2. 瓶颈分析

浏览器加载字体的默认行为是:

  1. 解析 HTML,发现 CSS。
  2. 解析 CSS,发现 @font-face
  3. 渲染 DOM 树。
  4. 遇到使用了该字体的文本节点时,才开始下载字体文件 (.woff2)。
  5. 下载期间,使用备用字体显示(或透明,取决于 font-display)。
  6. 下载完成,替换字体。

关键瓶颈在于第 4 步:延迟加载

🛠️ 3. 优化方案

3.1 方案一:Preload 预加载

告诉浏览器:“这个字体很重要,别等发现了再下,现在就下!”

index.html<head> 最顶部加入:

<link rel="preload" href="fonts/ClearSans-Bold.woff2" as="font" type="font/woff2" crossorigin>

注意crossorigin 属性是必须的,即使字体在本地。因为 Web 字体默认是跨域请求。

3.2 方案二:Base64 内联 (终极杀招) ✅

由于我们的字体文件是从本地 file://resource:// 加载的,IO 操作虽然快,但依然有异步延迟。
对于核心字体(如标题字体),我们可以直接将其转为 Base64 编码,嵌入到 CSS 文件中。

优点:CSS 加载完,字体就有了。0 延迟。
缺点:CSS 文件体积变大。但对于只有几 KB 的子集化字体(Subsetting),这完全可以接受。

@font-face {
    font-family: 'Clear Sans';
    font-weight: 700;
    /* 直接嵌入 Base64 */
    src: url('data:font/woff2;base64,d09GMgABAAAAAApQAA0AAAAAFWAAA...') format('woff2');
    /* 关键:swap 策略作为保底,但在 Base64 方案下其实用不上 */
    font-display: block;
}

3.3 方案三:Native 字体注入

如果多个 Web 页面共用同一套字体,为了避免重复加载,我们可以利用 ArkWeb 的 RegisterCustomFont (如有支持) 或者在 Native 层加载字体。

但在目前的混合架构中,更简单的做法是将字体文件放在 rawfile 中,通过 resource:// 协议共享。

🎨 4. 字体子集化 (Subsetting)

为了减小字体体积(从而减小 Base64 字符串长度),我们使用了 font-spiderpyftsubset 工具,只提取游戏中用到的字符(数字 0-9,字母 G, A, M, E, O, V, E, R…)。

效果
原始 .ttf 大小:2 MB
提取后 .woff2 大小:12 KB

这 12KB 的 Base64 嵌入到 CSS 中,几乎不影响解析速度,但换来了完美的 0 秒闪烁 体验。

📚 5. 总结

解决字体闪烁的核心思路是 “消除等待”

  1. 对于全量字体:使用 <link rel="preload">
  2. 对于关键标题/数字:使用 Subsetting + Base64 Inline
  3. 配置 font-display: block 避免文字隐形,或者 font-display: swap 接受闪烁但保证可见。

在 2048 项目中,我们选择了方案二,效果完美。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐