必须在:root中按平台硬编码完整字体栈并用class切换,因CSS变量无法动态拼接font-family,浏览器仅接受展开后合法的字符串token,且各平台系统字体兼容性差异大,硬编码比运行时判断更可靠。

直接在 :root 里用 CSS 变量存不同平台的字体栈,但必须按平台硬编码、带引号、末尾保留通用族名——变量只负责“整栈切换”,不参与拼接或运行时组装。
为什么不能用一个变量动态拼接系统字体?
浏览器解析 font-family 是静态的:它把整个值当做一个字符串 token 处理,var(--font-sans) 必须展开成完整、合法的字体栈(含引号和逗号分隔),否则会解析失败。常见错误包括:
-
--font-sans: -apple-system, sans-serif缺少引号 → 实际变成font-family: -apple-system, sans-serif,但-apple-system含连字符,不加引号会被当作无效标识符 -
font-family: var(--sys-font), var(--base-font)→ 浏览器不允许两个var()并列作为独立 font-family token,语法报错 - 把
system-ui放在自定义字体后面(如"Inter", system-ui, sans-serif)→ 在旧 Android 或 iOS 上 fallback 到难看默认字体,因为它们不识别system-ui
正确做法:按平台预设完整堆栈,用 class 切换
不同平台对“系统字体”的定义和兼容性差异大,硬编码比运行时判断更稳。例如:
:root {
--font-sans-macos: "-apple-system", "BlinkMacSystemFont", "Segoe UI", sans-serif;
--font-sans-win: "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;
--font-sans-android: "Roboto", "Noto Sans", sans-serif;
--font-sans-ios: "-apple-system", "Helvetica Neue", sans-serif;
}
body {
font-family: var(--font-sans-macos);
}
.platform-win body {
font-family: var(--font-sans-win);
}
.platform-android body {
font-family: var(--font-sans-android);
}
JS 检测平台后只改 document.body.className,不调用 style.setProperty。检测逻辑建议用 navigator.userAgent + navigator.platform 组合判断,避免仅靠 UA 字符串误判(如 Chrome on iOS 仍带 iPad)。
立即学习“前端免费学习笔记(深入)”;
如何让字体栈真正生效而不 fallback 到 Times New Roman?
关键不是“有没有变量”,而是“堆栈顺序是否符合平台真实支持情况”。容易被忽略的三点:
- macOS 和 iOS 必须把
-apple-system放第一位,且加双引号;漏掉引号或位置靠后,Safari 会跳过并 fallback 到Times - Windows 上
"Segoe UI"要前置,但必须跟"Roboto"或"Helvetica Neue"做降级,否则某些企业环境禁用 Segoe 时直接崩 - 所有堆栈末尾都必须显式写
sans-serif(不能省略),否则当所有前面字体都缺失时,浏览器可能 fallback 到serif(比如 IE11 或某些 Linux 发行版)
最易出问题的其实是安卓端:很多定制 ROM 不带 Roboto,"Noto Sans" 又没预装,所以 --font-sans-android 的最后一项必须是 sans-serif,而不是依赖某个具体字体名。


















