之前做前端页面时,遇到过不少和字体相关的问题:设计稿里指定了一款精致的标题字体,结果在同事电脑上打开,标题却变成了默认黑体;线上页面字体文件比较大,首屏文字先显示系统字体,加载完成后突然“闪”成目标字体;更麻烦的是,某些字体服务在部分网络环境下加载失败,页面所有使用该字体的元素全部降级。这些问题,其实都和一个关键机制有关——字体兜底(Font Fallback)。
本文以Knora One 字体为例,完整拆解字体兜底的概念、font-family 字体栈设计、@font-face 与 font-display 配置,并提供一个可以在本地直接运行的前端演示页面。无论你是刚接触 CSS 的新人,还是在维护复杂前端项目的开发者,这套方案都能直接复用。
1. 什么是字体兜底?为什么每个前端都要关注
1.1 字体加载失败的典型场景
先看几个真实场景。
场景一:用户在 Windows 上打开页面,系统里没有设计稿指定的 macOS 专用字体,浏览器找不到字体文件,只能使用默认字体渲染。
场景二:网页使用了一个在线字体服务,用户网络环境不稳定,字体文件请求超时,或者返回了 404,页面上的文字瞬间变成“裸奔”状态。
场景三:自定义字体文件体积很大,浏览器在字体没有加载完成前,先使用后备字体渲染文字。等字体加载完,又需要重新渲染,用户会看到文字跳动。
这些场景的核心问题不是“字体不好看”,而是字体资源在设备和网络环境中的不确定性。字体兜底机制,就是为字体设置一条或多条替代路径,保证用户无论处于什么环境,都能以可接受的视觉效果阅读内容。
1.2 什么是字体兜底
字体兜底,简单说就是浏览器按照我们指定的字体列表,从第一个开始尝试,如果这个字体不存在、加载失败,或者缺少需要的字形,就依次使用下一个字体。
在 CSS 里,它表现为这样:
font-family: "Knora One", "PingFang SC", "Microsoft YaHei", Arial, sans-serif;这段代码的意思依次是:
- 优先使用
Knora One; - 如果
Knora One不可用,尝试中文黑体类字体; - 再不行就使用系统通用无衬线字体;
- 最后一定会落到
sans-serif这个通用字体族,保证文字一定可以渲染。
所以,只要最后一个字体是通用字体族,页面上就永远不会有“找不到字体”的情况。这是字体兜底最重要的底线思维。
1.3 浏览器匹配字体的过程
在浏览器内部,字体匹配并不是一次性完成的,而是有一个不断回退的过程。
当浏览器遇到一个文本节点,比如“Knora One 字体兜底演示”,它会先查看font-family第一个字体名。如果该字体文件还没有加载完成,浏览器会根据font-display策略决定是等待还是先使用后备字体。如果字体不存在,或者存在但缺少中文字形,浏览器会继续向下查找字体栈中的下一个字体,直到找到具备所需字形的字体。
这里有一个容易忽略的细节:即使Knora One存在并且加载成功,如果你的文本包含中文字符,而Knora One字体文件里只有拉丁字母,没有中文字形,浏览器也会自动为中文部分启用兜底字体。这就是为什么字体栈里通常会把西文字体和中文字体放在一起,而不是只写一款字体。
2. 环境准备与字体文件管理
在写代码之前,先明确演示环境。由于字体兜底是纯前端 CSS 能力,本文不依赖框架,使用静态 HTML + CSS + 轻量 JavaScript 即可完成演示。
2.1 演示项目结构
建议创建以下目录结构:
knora-one-font-demo/ ├── css/ │ └── base.css ├── fonts/ │ ├── KnoraOne-Regular.woff2 │ └── KnoraOne-Regular.woff ├── js/ │ └── font-check.js ├── index.html └── README.md如果你的网络环境允许访问在线字体资源,可以跳过fonts/目录,直接使用在线链接。本文的完整演示会同时说明这两种方式。
2.2 字体文件格式与体积
现代 Web 字体主要有以下几种格式:
| 格式 | 说明 | 兼容性 |
|---|---|---|
| woff2 | 压缩率最高,推荐优先使用 | 现代浏览器均支持 |
| woff | 比 woff2 稍差,旧浏览器兼容性好 | 大多数浏览器支持 |
| ttf/otf | 桌面字体格式,体积大,Web 场景不推荐 | 兼容性好,但性能差 |
| eot | IE 时代格式,现在已经基本不需要 | 仅老 IE |
在正式项目中,通常只需要准备woff2和woff两种格式。@font-face中通过src的format()声明让浏览器选择。如果使用在线字体服务,比如 Google Fonts,服务方会通过 User-Agent 自动返回合适的格式,我们不需要手动管理格式。
2.3 三种字体引入方式对比
我把常见的前端字体引入方式整理成了下表。
| 引入方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 在线字体服务引入 | 接入简单,自动处理格式和浏览器兼容 | 依赖第三方网络,首屏可能会阻塞或延迟 | 快速原型、对网络要求不高的展示站 |
本地@font-face引入 | 可控性最强,无跨域问题,性能可优化 | 需要自行准备多个格式文件 | 生产环境、企业项目 |
| npm 包/构建工具引入 | 配置化、版本可管理 | 需要构建流程,配置复杂 | 大型前端工程 |
由于Knora One可能在不同平台上的获取方式不同,本文演示时分两种情况介绍:一种是通过在线字体服务引入,另一种是把字体文件下载到本地后用@font-face声明。两者最终都通过统一字体栈实现兜底。
2.4 浏览器开发者工具准备
字体相关问题需要使用浏览器开发工具排查,建议提前熟悉以下面板:
- Network 面板:查看字体文件请求是否成功、耗时多少、是否走缓存;
- Elements 面板 Computed 样式:查看元素最终使用的
font-family; - Console 面板:查看字体加载相关的警告或错误。
Chrome 和 Edge 可以通过地址栏输入edge://flags或chrome://flags查看字体相关实验特性;Safari 的 Web Inspector 也支持类似功能。版本不需要刻意追求最新,本文演示的是通用能力。
3. 核心概念拆解:字体栈与字体兜底
3.1 font-family 字体栈的匹配顺序
CSS 的font-family是可以接受一个列表的,这个列表俗称“字体栈(font stack)”。浏览器处理字体栈时,遵循一条原则:逐个检查,命中即用,未命中则继续向后查找。
这里的“命中”包含两层含义:
- 字体名称本身可以被浏览器识别;
- 字体文件包含当前文本所需的字形。
所以,在英文字体中放入中文字体时,英文部分会优先使用前面的西文字体,中文部分因为缺少字形,会自动向后匹配中文字体。
/* 推荐做法:西文在前,中文在后 */ font-family: "Knora One", "PingFang SC", "Microsoft YaHei", sans-serif;3.2 字体名是否加引号
这是新手最容易忽略的细节。CSS 规范规定,字体家族名如果包含空格或特殊字符,必须加引号。
/* 正确 */ font-family: "Knora One", sans-serif; /* 不建议,多个单词容易被解析冲突 */ font-family: Knora One, sans-serif;Knora One包含空格,所以必须写成引号包围的形式。一般来说,统一给所有自定义字体名加英文双引号是最稳妥的做法。
3.3 多语言与中文环境下的字体栈设计
在中文项目里,字体兜底通常要考虑西文字体和中文字体两类:
- 西文字体:用于数字、英文、标点,通常体积小,字形现代;
- 中文字体:用于汉字,体积大,通常依赖系统自带字体。
推荐的中文字体栈示例:
body { font-family: "Inter", "Knora One", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "Source Han Sans SC", sans-serif; }这个字体栈的设计思路是:
Inter是西文字体,英文数字显示更精致;Knora One作为特色西文/标题字体;PingFang SC是 macOS/iOS 系统默认中文字体;Hiragino Sans GB是 macOS 旧版中文字体;Microsoft YaHei是 Windows 常用中文字体;Noto Sans CJK SC、Source Han Sans SC是跨平台开源中文字体;sans-serif作为最终兜底。
注意,字体栈并不会导致重复下载。浏览器只会加载实际用到的字体文件,字体栈只是给浏览器提供选择范围。
3.4 font-family 与 unicode-range 的关系
unicode-range是@font-face中的一个高级属性,它告诉浏览器这个字体文件覆盖哪些 Unicode 码位范围。利用它,可以让字体在特定字符范围内生效,有时候还能让浏览器切分大字体文件,只加载需要的部分。
@font-face { font-family: "Knora One"; src: url("./fonts/KnoraOne-Latin.woff2") format("woff2"); unicode-range: U+0000-00FF; }上面的配置表示:Knora One字体文件只用于匹配U+0000-00FF范围内的拉丁字符。中文字符不在范围内,所以即使字体栈把Knora One放在第一项,浏览器也会为中文字符自动跳过它,继续查找后面的中文字体。
这是很多前端开发者忽略的细节,但它恰恰是字体兜底机制中非常实用的一环。通过合理设置unicode-range,可以避免中英文混排时重复加载大体积中文字体的问题。
4. @font-face 与 font-display 配置
要让Knora One成为页面可用的自定义字体,必须先通过@font-face声明字体资源。这一节把@font-face的关键参数和font-display的加载策略讲清楚。
4.1 @font-face 基本结构
@font-face { font-family: "Knora One"; src: local("Knora One"), url("./fonts/KnoraOne-Regular.woff2") format("woff2"), url("./fonts/KnoraOne-Regular.woff") format("woff"); font-weight: 400; font-style: normal; font-display: swap; }参数说明:
font-family:定义字体的名称,之后在 CSS 样式中可以直接使用;src:字体资源的来源,local()表示查找用户系统已安装的字体,url()表示网络或本地文件;format():声明字体格式,方便浏览器过滤不支持的格式;font-weight:声明该字体文件对应的字重;font-style:声明该字体文件对应的样式;font-display:控制字体加载期间文字的渲染策略。
4.2 font-display 的四个值
font-display是字体加载性能优化的核心属性之一。它定义了在字体文件加载过程中,浏览器如何渲染使用该字体的文字。
| 值 | 行为 | 适用场景 |
|---|---|---|
auto | 由浏览器决定,通常等同于block | 默认值,不推荐用于自定义字体 |
block | 字体加载期间文字保持不可见,可能造成“白屏”;加载完成后显示字体 | Logo、品牌标题等必须保持视觉统一的场景 |
swap | 先使用后备字体显示,字体加载完成后替换 | 正文、多数内容型页面 |
fallback | 短时间等待字体,如果加载完成则用自定义字体,否则长期使用后备字体 | 对性能敏感、字体加载不稳定的场景 |
optional | 等待时间极短,字体可用就用,不可用就不用了 | 弱网、用户体验优先场景 |
对于Knora One这类装饰性标题字体,比较推荐使用swap,这样既能保证文字可见,又能让字体加载完成后自然过渡。
4.3 local() 与用户本地字体
local()的作用是优先使用用户系统已经安装的字体。如果用户的系统里已经安装了Knora One,浏览器就不会发起网络请求,直接使用本地字体,速度最快。
@font-face { font-family: "Knora One"; src: local("Knora One"), url("./fonts/KnoraOne-Regular.woff2") format("woff2"); }不过local()使用起来有一点需要注意:恶意代码曾经利用local()探测用户本地安装了哪些字体,所以部分浏览器对local()的使用有限制。另外,如果本地字体版本和 Web 字体不一致,可能会造成显示差异。生产项目中,不能把local()作为唯一依赖,它只能作为性能优化手段。
4.4 font-weight 与可变字体
如果Knora One是一个可变字体(Variable Font),那么一个文件可以包含多个字重,配置方式略有不同:
@font-face { font-family: "Knora One Variable"; src: url("./fonts/KnoraOne-Variable.woff2") format("woff2-variations"); font-weight: 100 900; font-display: swap; }可变字体的好处是只加载一个文件就能使用font-weight从细到粗的任意值,节省流量,也让字体调节更灵活。但不是所有字体都提供可变版本,使用前需要确认字体文件类型。
5. 完整实战:Knora One 字体兜底功能演示
这一节从零搭建一个可运行的演示页面。页面会展示两个区块:一个使用完整的字体栈,另一个使用“最差兜底”写法,对比字体加载前后和兜底前后的表现。
5.1 创建 HTML 页面结构
在项目根目录创建index.html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Knora One 字体兜底功能演示</title> <link rel="stylesheet" href="./css/base.css"> </head> <body> <main class="demo"> <section class="card"> <h1>Knora One 标题字体</h1> <p class="font-target"> 让每一行文字,都有备用方案。 </p> <p class="font-target"> Font fallback is a basic front-end skill. </p> <p class="status" id="fontStatus">正在检测字体加载状态...</p> </section> <section class="card compare"> <h2>对比区域</h2> <div class="compare-item"> <h3>完整字体栈</h3> <p class="font-stack-good"> Knora One + 中文字体 + sans-serif </p> </div> <div class="compare-item"> <h3>缺失字体栈</h3> <p class="font-stack-bad"> 仅指定 Knora One,无任何兜底 </p> </div> </section> </main> <script src="./js/font-check.js"></script> </body> </html>这个页面包含两个区块:
- 第一块是主要演示区,使用完整字体栈;
- 第二块是对比区,使用完整字体栈和缺失字体栈两种写法,方便直观看到差异。
5.2 编写 base.css 定义字体栈与兜底配置
创建css/base.css:
/* 字体声明 */ @font-face { font-family: "Knora One"; src: url("../fonts/KnoraOne-Regular.woff2") format("woff2"), url("../fonts/KnoraOne-Regular.woff") format("woff"); font-weight: 400; font-style: normal; font-display: swap; } /* 基础样式 */ * { box-sizing: border-box; } body { margin: 0; font-family: "Knora One", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; background: #f5f7fa; color: #333; line-height: 1.8; } .demo { max-width: 960px; margin: 40px auto; padding: 0 20px; } .card { background: #fff; border-radius: 12px; padding: 32px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); } h1, h2, h3 { font-family: "Knora One", "PingFang SC", "Microsoft YaHei", sans-serif; } /* 主要演示区 */ .font-target { font-size: 24px; margin-bottom: 16px; } /* 对比区域 */ .compare { display: flex; flex-wrap: wrap; gap: 16px; } .compare-item { flex: 1 1 300px; padding: 20px; border: 1px solid #e0e3e8; border-radius: 8px; background: #fafbfc; } /* 完整字体栈 */ .font-stack-good { font-family: "Knora One", "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 20px; } /* 缺失字体栈:只写了 Knora One,没有兜底 */ .font-stack-bad { font-family: "Knora One"; font-size: 20px; }注意,.font-stack-bad中只写了"Knora One",没有通用字体族。这样如果Knora One加载失败,浏览器会使用用户代理的默认字体,而不是我们指定的优雅兜底字体,这是用来演示“兜底缺失”的反面教材。
5.3 方式一:通过在线字体服务引入 Knora One
如果你是从 Google Fonts 或类似字体平台加载Knora One,方式略有不同。以 Google Fonts 为例,通常只需要在 HTML 的<head>中加入两段代码。
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Knora+One&display=swap" rel="stylesheet">然后直接在 CSS 中使用:
body { font-family: "Knora One", "PingFang SC", "Microsoft YaHei", sans-serif; }使用在线字体服务时,浏览器会加载该平台返回的 CSS 文件,里面已经包含了@font-face声明和unicode-range。display=swap参数会对应生成font-display: swap的 CSS,不需要再自己写@font-face。
不过需要注意,并不是所有字体平台都收录Knora One。如果平台没有这个字体,就需要使用下面的本地引入方式。
5.4 方式二:本地 @font-face 引入
使用本地字体文件时,按下面的步骤操作:
- 进入
fonts/目录; - 放置
KnoraOne-Regular.woff2和KnoraOne-Regular.woff两个文件; - 在
base.css中引入@font-face。
如果你的字体文件命名和目录不一致,请同步修改base.css里的url()路径,否则浏览器会因为找不到文件而直接跳过该字体,触发兜底机制。
本地方式的优点是不依赖第三方网络,部署到内网或离线环境都可用。缺点是如果字体允许范围内没有woff2文件,需要从设计资源中转换。转换工具可以使用fonttools、woff2命令行工具,或者在线转换站点。注意遵守字体的开源许可证,确认允许 Web 嵌入。
5.5 用 JavaScript 验证字体加载状态
为了让“兜底是否生效”可视化和可验证,我们使用浏览器提供的FontFace相关 API 监听字体状态。
创建js/font-check.js:
function checkFontLoaded(fontName) { const fontSpec = `16px "${fontName}"`; // document.fonts.check 返回当前字体是否已经加载完成 return document.fonts.check(fontSpec); } function updateStatus() { const statusEl = document.getElementById("fontStatus"); if (!statusEl) { return; } const loaded = checkFontLoaded("Knora One"); if (loaded) { statusEl.textContent = "Knora One 字体已加载,当前使用自定义字体渲染。"; statusEl.style.color = "#2e7d32"; } else { statusEl.textContent = "Knora One 字体尚未加载或加载失败,当前可能使用兜底字体。"; statusEl.style.color = "#c62828"; } } // 页面初始检查 updateStatus(); // 监听字体加载完成事件 if (document.fonts) { document.fonts.ready.then(function () { updateStatus(); }); }这段代码的核心是document.fonts.check()。它接受一个 CSS 字体值,比如16px "Knora One",并返回一个布尔值,表示该字体是否加载完成。document.fonts.ready则是一个 Promise,在页面所有字体资源加载完成后 resolve。我们可以利用它在字体加载完成后重新更新页面状态。
如果你的字体文件名不同,只需要修改checkFontLoaded("Knora One")中的字体名即可。
5.6 运行与预期结果
在项目根目录启动一个静态服务器:
# 使用 Python 3 python3 -m http.server 8080或者使用 Node.js 的serve:
npx serve .然后打开浏览器访问http://localhost:8080。
预期结果:
- 如果本地
fonts/目录中确实有字体文件,页面标题会使用Knora One渲染; - 加载完成前,由于
font-display: swap的配置,文字会先以兜底字体显示,加载完成后切换; - 控制台状态区域会从“未加载”变为“已加载”;
- 对比区域中,完整字体栈在
Knora One不可用时会使用中文字体,而缺失字体栈的写法在字体加载失败后会使用浏览器默认字体,效果差异明显。
你可以通过开发者工具中的 Network 面板模拟离线状态,刷新页面观察两种区域的变化,这就是字体兜底最直观的演示。
6. 常见问题与排查思路
字体相关的问题排查起来往往比功能逻辑更繁琐,因为涉及的环节多:字体文件、CSS 解析、网络请求、浏览器缓存、系统字体。下面整理了一份高频问题清单。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 字体完全没有生效,全部使用默认字体 | font-family写错,或@font-face没有声明 | 检查选择器是否匹配、字体名是否带引号、@font-face是否在样式表中正确声明 |
| 字体请求返回 404 | 字体文件路径错误,或文件名大小写不一致 | 检查项目目录中的文件路径,确保url()相对路径正确 |
| 字体生效了,但中文部分显示异常 | 自定义字体文件只包含西文字形,没有中文字形 | 在字体栈中补充中文字体,示例:"Knora One", "Microsoft YaHei" |
| 加载期间文字不可见,出现白屏 | font-display默认值为auto/block,文字等待字体加载 | 显式设置font-display: swap或fallback |
| 使用在线字体服务跨域请求失败 | 字体 CDN 未正确配置跨域头 | 确认字体服务地址是否支持跨域,本地调试可使用本地字体文件 |
| 字体加载很快,但 Network 面板出现两次请求 | 浏览器预加载和实际使用字体时机不同 | 可接受,或使用<link rel="preload">提前加载,注意crossorigin属性 |
| 部署后字体失效,本地正常 | 服务器 MIME 类型或缓存配置问题 | 确认服务器对.woff2返回font/woff2类型,检查缓存策略 |
| 字体在 Chrome 正常,Safari 不显示 | woff2 格式支持差异或字体名称匹配问题 | 提供woff格式作为兼容,检查local()是否干扰 |
| 修改了字体文件但页面不更新 | 浏览器缓存了旧字体文件 | 清理缓存,或在字体 URL 后添加版本参数,如?v=2 |
排查字体问题时,我一般按下面的流程走:
- 打开 Network 面板,刷新页面,看字体文件请求是否发出、状态码是不是 200;
- 查看字体文件响应内容类型是否正确;
- 在 Elements 面板选中文字元素,查看 Computed 里的
font-family,确认实际生效的是哪个字体; - 在 Console 里执行
document.fonts.check('16px "Knora One"'),确认字体是否已加载完成; - 最后检查系统是否安装过同名旧字体,必要时移除或更换字体名。
7. 最佳实践:设计高可用的字体兜底方案
字体兜底不是“写一个字体栈就完事”,而是一整套性能与兼容性的考量。下面是我在实际项目中沉淀下来的几个最佳实践。
7.1 字体栈至少保留 4 级
一级是目标字体,二级是风格近似的系统字体,三级是中文/对应语言字体,四级是通用字体族。
font-family: "Knora One", "Arial Rounded MT Bold", "PingFang SC", "Microsoft YaHei", sans-serif;这样写的意义是:目标字体加载失败时,先找一个风格接近的系统字体替代,再保证中文正常,最后由通用字体族兜底。每一层解决一个层面的问题。
7.2 使用 font-display: swap,但注意闪烁
swap会让首屏更快,但会带来“字体切换闪烁”的问题。对于偏展示的标题可以将font-display设为block,对于正文则用swap或fallback。一个折中方案是给标题元素使用较小的font-size,减少切换时的视觉冲击。
7.3 字体文件要压缩和子集化
中文字体动辄几 MB,如果加载整包字体,首屏性能会非常差。实践中应只加载页面用到的文字子集。比如页面固定展示几个标题词,可以把这些字单独提取成一个子集字体文件。西文字体通常本身较小,但也可以按unicode-range进一步拆分。
7.4 使用 preload 对关键字体预加载
对于页面上最重要的标题字体,可以显式预加载:
<link rel="preload" href="./fonts/KnoraOne-Regular.woff2" as="font" type="font/woff2" crossorigin>preload告诉浏览器这个资源是当前页面必需的,请在遇到 CSS 引用前就尽早加载。注意crossorigin属性对字体请求通常是必需的,即使字体资源来自同源。
7.5 尊重用户的系统偏好
近年来系统都支持“减弱动态效果”和“减少透明度”等辅助功能设置,字体加载上也有人提倡使用prefers-reduced-data来减少数据消耗。可以利用媒体查询降低非关键字体资源的加载优先级:
@media (prefers-reduced-data: reduce) { .font-target { font-family: "PingFang SC", "Microsoft YaHei", sans-serif; } }虽然这个媒体查询并非所有浏览器都支持,但它代表了一种设计趋势:在保证基本体验的前提下,减少非必要资源消耗。
7.6 缓存与版本管理
生产环境中,字体文件应该使用长效缓存。部署新版本字体时,通过 URL 查询参数改变文件名引用:
src: url("../fonts/KnoraOne-Regular.woff2?v=20250101") format("woff2");这样既能利用缓存,又能在升级字体后强制浏览器获取新文件。
8. 总结与延伸学习
本文围绕Knora One字体,系统梳理了字体兜底的完整链路:从font-family字体栈的匹配机制,到@font-face的资源配置,再到font-display的加载策略,最后通过一个可运行的 HTML/CSS/JavaScript 页面完成了功能演示。可以说,掌握字体兜底机制,是前端开发者处理页面稳定性、兼容性和性能问题的基本功之一。
如果你要继续深入,可以从这几个方向入手:
- 学习 CSS
@font-face的unicode-range特性,设计更精细的多语言字体系统; - 了解可变字体的制作与部署,体验一个文件控制多个字重的灵活性;
- 研究字体加载性能指标,包括 FOIT、FOUT、CLS 之间的关系;
- 使用构建工具比如 Vite 或 Webpack 把本地字体资源自动打包并配置 hash 版本号。
字体兜底看似只是font-family一行代码,但真正把它做对、做细,需要同时理解浏览器的字体渲染机制、网络加载策略、资源体积控制和多语言环境。建议你把本文的演示页面下载到本地,故意删掉字体文件,再刷新一次页面,亲手感受一下“有兜底”和“没有兜底”的差异。这样的体验,比记住任何一条 CSS 规则都更深刻。
如果本文对你有帮助,可以收藏备用。后续遇到了新的字体兼容问题,也欢迎在评论区留下你踩过的坑,一起交流解决方案。