打开网站,F12调出开发者工具,切到 Network 面板,刷新一下——盯着“font”那一栏看两秒。是不是有个几 MB 的文件排在最后才开始下载?用户点进来,页面卡着不动,等三四秒才突然“啪”一下弹出文字?这真不是错觉,是字体在偷偷拖后腿。
字体加载为什么会卡住页面?
浏览器默认会等字体下载完,才肯把文字画出来。你写的标题、正文,全被按在“待命区”,一个字不给露。这叫 FOIT(Flash of Invisible Text):用户面对的是一片空白,不是加载动画,不是骨架屏,就是纯白。
中文字体尤其狠。一个常规的思源黑体或 Noto Sans SC,随便加几个字重,轻松破 2MB。而用户打开你页面的第一眼,可能只看到“欢迎”“最新文章”“联系我们”这几个词——结果得为几千个汉字买单。
我之前优化过一个技术博客,首页标题用了 Google Fonts 的 Noto Sans SC,一口气加载了 300/400/500/700 四个字重。实测下来,在弱网环境下,首屏文字要等 4 秒才出现。后来只留 400 和 700,砍掉一半体积,首屏内容立刻提前了一大截。不是设计妥协了,是用户根本没机会看到那两个不用的字重。
解法很简单:在 @font-face 里加上 font-display: swap。
意思是:先用系统字体把字打出来,等你的字体下好了,再悄悄换掉。
会有那么一丁点闪动(FOUT),但比干等强太多。用户 0.3 秒看到文字,和 3 秒后看到“完美排版”,选哪个?答案写在跳出率里。
4个方法:从源头减少字体文件体积
别让一个字体文件,承担它不该背的锅。中文字体大,是因为塞进了太多用户永远用不到的字。你首页就八个汉字,真没必要让用户下载 5MB 的全量包。
方法1:只加载实际用到的汉字。
Google Fonts 支持 &text= 参数,比如 &text=欢迎来到我的博客,它就只生成这 8 个字的字形。我帮一个活动页做过子集化,全站固定文案共 30 个汉字,字体体积从 2.8MB 直接压到 40KB,加载几乎无感。本地可用 FontTools 的 pyftsubset,或者直接上 Font Squirrel 的在线子集工具。
方法2:强制用 WOFF2 格式。
WOFF2 是目前压缩率最高的网页字体格式,比 WOFF 小 30%~50%,比原始 TTF 小得更离谱。现代浏览器全支持,你只要在 @font-face 里优先声明 WOFF2,再 fallback 到 WOFF 就行。TTF 别放了,那是给本地安装用的,不是给网页跑的。
方法3:删掉不用的字重。
Regular(400)和 Bold(700)覆盖了 95% 的日常场景。Light、Medium、SemiBold 这些,除非你在做字体展示页,否则大概率只是躺在 CSS 里吃灰。我见过一个设计站加载了 6 个字重,结果全站 90% 的文本只用了其中两个——剩下四个字重,纯属帮用户多下 1MB 流量。
方法4:试试系统字体栈。
如果标题不用手写体、正文不用衬线体,那就直接用系统字体:
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
GitHub、Medium、Vercel 官网都这么干。不是省事,是快——零下载、零阻塞、适配每台设备。你花三天调字体间距,不如让用户早 800ms 看见第一行字。
用 preload 和 preconnect,让浏览器提前干活
字体加载慢,很多时候不是因为文件大,而是浏览器“反应太慢”。它得先下载 CSS,再解析里面 @font-face,再发起字体请求——中间卡了两轮 HTTP 往返。
preload:在 HTML 的 <head> 里加一行:
<link rel="preload" href="/fonts/title.woff2" as="font" crossorigin>
告诉浏览器:“这个字体很急,别等 CSS,现在就去下。” 我在一个电商详情页试过,产品标题字体加了 preload 后,文字渲染不再“先白后跳”,加载节奏稳多了。
preconnect:如果你用的是 Google Fonts、Adobe Fonts 或其他第三方字体服务,在 <head> 里补一句:
<link rel="preconnect" href="https://fonts.googleapis.com">
浏览器会提前建好 TCP 连接、完成 TLS 握手,等真正要下字体时,直接发请求就行。弱网下效果最明显——少一次 DNS 查询,就能抢回几百毫秒。
注意:preload 别贪多。一个页面最多 preloader 两个字体文件,通常是标题 + 正文用的那两个。全 preload?等于让浏览器同时扛着几座山跑,反而堵死其他资源。
用 CSS font-display:控制加载失败时的降级策略
font-display 有五个值,但你真正需要的就三个:
swap:立刻上系统字体,下好了再换。安全、简单、见效快。适合正文、列表、按钮这类“必须可读”的地方。用户可能察觉不到切换,但一定能感觉到“怎么这次字出来得这么快”。
fallback:等 100ms,没下好就先用系统字体;之后再给 3 秒窗口期,能换就换,换不上就算了。适合标题、卡片标题这类“好看加分,没了也不影响理解”的位置。既避免长白屏,又减少突兀闪动。
optional:100ms 内没下好,直接放弃。适合图标字体、装饰性大标题、背景文字——它们不上也没关系,内容本身才是主角。
我的习惯是:正文 swap,标题 fallback,icon font optional。不是教条,是根据“用户看什么、为什么看”来分配优先级。
实际案例:一个博客站的字体优化全流程
去年接手一个技术博客,作者特别喜欢一款手写体标题字体,原方案是直接引用完整 TTF(3.6MB),font-display: auto。结果移动端首屏平均要等 5 秒以上,不少用户划走前根本没等到文字出现。
我们做了五件事:
- 用
pyftsubset提取所有文章标题里真实出现过的汉字(约 200 个),字体体积降到 120KB; - 转成 WOFF2,再压到 70KB;
@font-face加上font-display: swap;- 在
<head>里preload这个 WOFF2 文件; - 字体托管在自有 CDN,不用 preconnect 第三方域名。
改完上线当天,Lighthouse 的“最大内容绘制(LCP)”指标从 5.2s 降到 1.4s。用户反馈只有两句:“好像快了?”“标题看着没啥不一样啊。”——这恰恰是最好的结果:字体没成为焦点,内容成了主角。
今天就能执行的3个操作步骤
打开 Chrome,进你网站,按 F12 → Network → 筛选 “font”。看看有没有超过 300KB 的字体文件?加载时间是不是排在 CSS 后面?截图记下来,这就是你的优化起点。
打开你项目的 CSS 文件,找到所有
@font-face规则,在里面加一行font-display: swap;。如果是 Google Fonts,直接在链接末尾加&display=swap。改完保存,刷新页面——白屏消失了,就是这一步的功劳。打开你网站的 HTML 模板(比如
_layout.html或index.html),在<head>里加一行 preload:
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
路径替换成你实际用的 WOFF2 文件地址。只加一个,别多。然后清缓存刷新,回到 Network 面板,确认字体请求是不是提前到了最前面。如果还是慢,说明文件还能再压——回头做子集化。