我拿掉了中文网页字体,每一页轻了 1 MB

这个网站的每一个中文页面,在读者看到第一个字之前,要先下载大约 1 MB 的字体。我把它换成读者设备里本来就有的字体。这里是前后的数字、那几行 CSS、我放弃了什么,以及它没有解决的事。

其他语言版本 English

一个中文页面原来背着什么

改动之前,这个网站的每一个中文页面,在请求任何字体文件之前,HTML 里就先带着 94,098 字节的字体说明。那是 100 条 @font-face 规则,每一页都是同样的 100 条。

这个网站是双语的。每一页都有英文版和简体中文版,所以英文页正好是中文页的对照组。版式一样,图片一样,只是文字换了语言。

英文版的嘛嘛档那篇文章,HTML 是 51 KB。中文版是 143 KB。HTML 到了之后,中文页还要再下载 20 个字体文件,一共 1,107 KB。中文首页要下载 16 个,857 KB。

在手机上,Lighthouse 给那篇中文文章跑了两次,55 分和 65 分。英文版两天前测的是 97 分。

一位在吉隆坡用手机流量的读者,为了读一篇文章,先付了一兆的字体。

中文字体为什么这么重

拉丁字母的字体只需要几百个字形。中文字体需要成千上万个,而每一个都是一幅画。

Google Fonts 在这件事上已经做得很好。它把一套中文字体切成大约一百片,告诉浏览器哪些字在哪一片里。那 100 条规则就是这个。浏览器只下载页面上用到的字所在的那几片。

问题是,一篇普通的中文文章,用到的字会散落在很多片里。我的一篇文章就需要 20 片。切片让我不用下载好几兆的整套字体,但还是留下了一兆。

还有一个容易忽略的代价:那 100 条规则本身。为了让字体早一点开始加载,它们被直接写进每一个中文页面,结果每个中文页面在任何字体到达之前,就已经是英文版的将近三倍大。

改动:四行 CSS

解决办法是不再发送中文字体,而是点名读者已经有的那些。每一台 iPhone、iPad 和 Mac 都自带苹方(PingFang SC)。Android 自带 Noto Sans CJK,跟我原来下载的是同一套设计。Windows 自带微软雅黑。

html[lang='zh-Hans'] {
  --font-zh: 'PingFang SC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Noto Sans SC', 'Source Han Sans SC', 'Microsoft YaHei', sans-serif;
  --font-sans: 'Geist Variable', var(--font-zh);
}

第三行把拉丁字体排在最前面,所以中文里夹着的英文和数字,仍然用这个网站自己的字体 Geist 来画。只有遇到汉字,浏览器才往下找这份中文清单。

样式表里的改动就这么多。其余的都是删除:构建配置里的字体项、加载它的那个标签,还有跟着它来的 100 条规则。

我用两个 Claude 会话工作,一个 Brain 负责规划,一个 Hand 负责建造。这个改动是 Brain 提的。我没有凭想法就点头。我是在 Hand 量过、把两个版本的截图并排放好之后才同意的。

前后对比

改动之后,中文页面请求的字体文件和英文页面完全一样,就是两个。下面是中文版嘛嘛档文章的数字,用 Lighthouse 手机模式,同一台机器、同一天,各跑两次。

改动前 改动后
字体文件 20 个 2 个
字体大小 1,107 KB 54 KB
HTML 143 KB 51 KB
HTML(压缩后) 48 KB 15.6 KB
整个页面 1,464 KB 318 KB
最大内容绘制(LCP) 3.7 秒和 4.4 秒 2.9 秒和 2.2 秒
Lighthouse 手机分数 68 和 73 84 和 97

中文首页从 1,796 KB 降到 901 KB,分数从 40 和 42 升到 51 和 55。它还是偏低,原因我知道:首页跑着一个 3D 场景,那是我另外写过的一笔代价。字体占了那一页重量的一半,但不是全部的问题。

我放弃了什么

我放弃的是控制:同一个页面,现在在 iPhone、Android 手机和 Windows 电脑上,看起来会有一点不同。用网页字体的时候,每一位读者看到的字形都一样。

在我同意之前,有一件事必须先查清楚:粗体是不是真的粗体。如果系统字体只有一个字重,浏览器会把常规字形抹粗来冒充粗体,看得出来。在 Mac 上,浏览器画出来的是真的字重:正文是苹方常规体,标题是中黑体,粗体是中粗体。

写这篇文章的时候,我还没有亲眼在 Windows 或 Android 手机上看过这个网站。微软雅黑的字重比苹方少,所以那边的标题可能比我设计的要轻一点。这个我接受。一个到得了的页面,比一个长得一样的页面更有价值。

它没有解决的事

上线后第一次实测是 76 分,不是 97 分。最大的那个元素花了 5.6 秒才出现。我省下了一兆,手机分数却几乎看不出来。

我去找原因,也确实找到一个。同一天我改了分析工具的加载方式,两个 Google 脚本加起来 297 KB,开始跟着每一页一起加载,跟内容抢路。我把它们改成等页面空闲之后、或者读者第一次触碰页面时才加载。之后在同一篇中文文章上实测三次:92、97 和 99。

但那两个脚本解释不了 76 分。在这个修正上线之前,同一个页面、同一份代码又测了两次,是 95 和 92。那个 76 分,多半只是一次跑得差。挪开脚本让平常的分数高了几分,不是二十分。

从这里我学到两件事。一次测量不算结果:同一个页面、同一份代码,相隔几个小时就测出 76 和 95,而我差点就照着 76 写出一个故事。还有,一个页面上不止压着一样重物。搬走最大的那一样,多半只是让下一样露出来。

还有一件小事没解决。中文里,一行不应该以「的」这样的助词开头。CSS 有一个属性,本意是按词组来断行。我试了。在我测过的浏览器里,它对中文不起作用。我就先放着。

怎么检查你自己的网站

大约两分钟,不用任何付费工具,你就能知道自己的中文页面下载了什么。

  1. 在电脑上用 Chrome 打开你网站的一个中文页面。
  2. 打开开发者工具,进入 Network(网络),选 Font(字体)筛选,然后重新加载。
  3. 数一数文件,看底部的总大小。如果是二十个文件或者一兆,那你的情况跟我原来一样。
  4. 查看网页源代码,搜索 @font-face。如果有几十条,说明你的 HTML 也背着字体说明。
  5. 用另一种语言打开同一个页面,比较两边的总数。差出来的,就是你的中文读者多付的。

如果数字很大,先在一个页面上试试上面那份字体栈,用你自己的手机看一看,再用眼睛决定。

常见问题

中文网站应该用网页字体还是系统字体?

正文用系统字体。中文网页字体即使切片切得好、加载得好,每一页也大约要一兆,而每一台现代手机和电脑都已经装了很好的中文字体。只有在很短的标题或标志上,字形本身就是品牌的时候,才保留网页字体,而且要先量过它的代价。

为什么 Noto Sans SC(思源黑体)加载这么慢?

因为中文需要成千上万个字,每一个都是一幅画。Google Fonts 把 Noto Sans SC 切成大约一百片,页面只下载用到的部分,但普通文章还是会碰到很多片。这个网站上一篇普通的文章,需要 20 个字体文件,1,107 KB。

简体中文的 CSS font-family 应该怎么写?

这个网站用的是 "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei", sans-serif,前面再放拉丁字体。它依次照顾到苹果设备、Android 和 Windows,最后落到浏览器自己的默认字体。

用系统字体会不会让中文网站变难看?

它会让网站在每台设备上略有不同,不是变难看。苹方、Noto Sans CJK 和微软雅黑,就是读者每天在自己手机的每个应用里看到的字体。检查一下粗体用的是不是真的粗体字重,再在真机上看过,然后才决定。