uni-app 项目此前使用系统字体栈,同一条文案在三端落到不同字体,字形、字重、基线均不一致。本文记录字体体系的三次改动:全局系统字体栈引发的样式回归及其修复、子集化内置 MiSans 统一三端字形、为大字号标题补充 Light 字重。
一、样式回归:全局字体改动的问题
首次改进将全局字体改为系统 UI 字体优先(SF Pro、Roboto、HarmonyOS Sans、苹方、微软雅黑、Noto Sans CJK 等回退),并设置全局基准字号 28rpx、行高 1.5、统一抗锯齿。上线后 Android 真机与 H5 端出现文字观感劣化:单行内字重与基线不统一,中英文混排尤为明显。
两处原因:
1. 字体栈顺序错误
原字体栈将纯拉丁字体排在中文字体之前:
1 | // system-ui / Segoe UI / Roboto 均不含中文字形 |
CSS 字体匹配按字符逐个回退:英文命中 Segoe UI/Roboto 即停止,中文继续向后命中雅黑/苹方。同一行内两种字体混排,字重、字宽、基线均不一致。
2. 全局排版属性过强
在 page, body, uni-page-body 上声明的 font-size: 28rpx、line-height: 1.5 级联到所有未显式声明字号的元素,H5 端 rpx→rem 换算进一步放大影响;-webkit-font-smoothing: antialiased 使移动端字重显得偏细。
修复:
- 字体栈改为中文优先,删除不含中文字形的
system-ui、"Segoe UI"、Roboto、"SF Pro Text"、"SF Pro Display"; - 移除全局
font-size、line-height与抗锯齿声明,字号交还各页面的字号 token($font-xs~$font-2xl)管理。
二、内置 MiSans:子集化统一三端字形
系统字体栈修复后,字形不一致的根因仍在。最终方案为内置 MiSans。
体积约束
MiSans 官方包单字重体积:ttf 7.75 MB、woff2 4.63 MB,可变字体 20 MB;项目涉及 300~800 共 6 档字重,全量引入超过 14 MB,必须子集化。
字符集取 GB2312 一级汉字(3755) + 符号区(682) + ASCII + 项目源码出现过的字符,去重后约 4556 字(源码字符用于兜住 「」、… 等 GB2312 之外的符号)。使用 fonttools 裁剪:
1 | pyftsubset MiSans-Regular.ttf --text-file=tmp/charset.txt \ |
三档字重(Regular / Medium / Demibold)woff2 合计 1.48 MB,每档约 500 KB、4765 字形。
字重映射
字重按实测墨量排序,与文件名直觉不同——Semibold 实际重于 Demibold,已接近 700:
1 | Thin 5.1% < ExtraLight 9.0% < Light 12.6% < Normal 16.5% |
因此 400/500/600 取 Regular / Medium / Demibold。声明字重覆盖了 700、800 的匹配区间,不会触发浏览器合成加粗。
三端支持与落地
| 平台 | 本地字体 | 方案 |
|---|---|---|
| H5 | 支持 | @font-face + /static/ 绝对路径 |
| App | 支持 | 同上,要求 compilerVersion >= 3 |
| 小程序 | 不支持 | 条件编译排除,回退系统字体 |
落地位置:fonts.scss 注册 @font-face(font-display: swap,#ifdef H5 || APP-PLUS 包裹),variables.scss 将 $font-family-sans 首位改为 "misans" 并保留系统栈兜底。
两个实现细节:
- family 名必须小写——uni-app 会强制将
@font-face的 family 值转为小写; - 字体路径必须使用
/static/绝对路径——相对路径在小程序端被忽略。
子集外生僻字回退系统字体,”一行内两种字体”出现概率从 100% 降至约 0.3%,且不产生豆腐块;字体文件加载失败时同样回退系统字体,可降级。
三、Light 字重:font-weight 失效的成因
内置后发现导航栏标题(24px,全页最大字号叠加最深字色)观感偏粗,尝试 font-weight: 300 无效,渲染与 400 完全相同。三层原因:
- 已有三档 woff2 为静态子集,无
fvar/gvar/STAT表,font-variation-settings: 'wght' 300无效; - CSS 字体匹配在请求 300 时找不到 ≤300 的字面,向上取最接近的 400;
- 浏览器只合成粗体,不合成细体。
(实测 MiSans-Regular 墨量比微软雅黑 Regular 低约 10%,标题偏粗是字号、字色、笔画三者叠加的结果。)
解决方案为引入独立字面 misans-light.woff2(496 KB,字符集与其余三档一致),注册独立 family 而非元素级 font-weight——后者违反仓库”禁止元素级 font-weight”的规范:
1 | $font-family-display: "misans-light", $font-family-sans; |
导航标题与明细页切换控件改用 $font-family-display,标题墨量由 0.3083 降至 0.2120(−31%)。字体总量由 1.48 MB 增至约 2.0 MB,仍为按需加载。
四、验证清单
- H5 DevTools 确认 woff2 按需加载、无 404;
- Android 真机确认 MiSans 生效——三端中唯一有历史问题的路径,未生效时改用
uni.loadFontFace逐页注册或补 ttf; - 检查中英文混排文本同行内字体与基线一致;
- 检查 500/600/700 字重梯度正常、无合成加粗;
- 输入子集外生僻字,确认回退系统字体且无豆腐块;
- 逐页确认标题字重一致;
- 确认仓库无新增元素级
font-weight。
总结
两次关键结论:
- 字体栈混排中英文时,不含中文字形的拉丁字体不能排在中文之前,否则逐字符回退会导致同行字重与基线不一致;
- 静态子集字体上的
font-weight是无效声明——浏览器就近取档且只合成粗体,需要更细字重必须引入对应字面。
条评论