抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

uni-app 项目此前使用系统字体栈,同一条文案在三端落到不同字体,字形、字重、基线均不一致。本文记录字体体系的三次改动:全局系统字体栈引发的样式回归及其修复、子集化内置 MiSans 统一三端字形、为大字号标题补充 Light 字重。

一、样式回归:全局字体改动的问题

首次改进将全局字体改为系统 UI 字体优先(SF Pro、Roboto、HarmonyOS Sans、苹方、微软雅黑、Noto Sans CJK 等回退),并设置全局基准字号 28rpx、行高 1.5、统一抗锯齿。上线后 Android 真机与 H5 端出现文字观感劣化:单行内字重与基线不统一,中英文混排尤为明显。

两处原因:

1. 字体栈顺序错误

原字体栈将纯拉丁字体排在中文字体之前:

1
2
// system-ui / Segoe UI / Roboto 均不含中文字形
font-family: system-ui, "Segoe UI", Roboto, "SF Pro Text", ..., "微软雅黑", ...;

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
2
3
pyftsubset MiSans-Regular.ttf --text-file=tmp/charset.txt \
--flavor=woff2 --layout-features='*' --no-hinting --desubroutinize \
--drop-tables+=DSIG --output-file=static/fonts/misans-regular.woff2

三档字重(Regular / Medium / Demibold)woff2 合计 1.48 MB,每档约 500 KB、4765 字形。

字重映射

字重按实测墨量排序,与文件名直觉不同——Semibold 实际重于 Demibold,已接近 700:

1
2
3
Thin 5.1% < ExtraLight 9.0% < Light 12.6% < Normal 16.5%
< Regular 18.5% < Medium 20.4% < Demibold 23.2%
< Semibold 26.2% < Bold 30.3% < Heavy 32.6%

因此 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 完全相同。三层原因:

  1. 已有三档 woff2 为静态子集,无 fvar / gvar / STAT 表,font-variation-settings: 'wght' 300 无效;
  2. CSS 字体匹配在请求 300 时找不到 ≤300 的字面,向上取最接近的 400;
  3. 浏览器只合成粗体,不合成细体。

(实测 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,仍为按需加载。

四、验证清单

  1. H5 DevTools 确认 woff2 按需加载、无 404;
  2. Android 真机确认 MiSans 生效——三端中唯一有历史问题的路径,未生效时改用 uni.loadFontFace 逐页注册或补 ttf;
  3. 检查中英文混排文本同行内字体与基线一致;
  4. 检查 500/600/700 字重梯度正常、无合成加粗;
  5. 输入子集外生僻字,确认回退系统字体且无豆腐块;
  6. 逐页确认标题字重一致;
  7. 确认仓库无新增元素级 font-weight。

总结

两次关键结论:

  1. 字体栈混排中英文时,不含中文字形的拉丁字体不能排在中文之前,否则逐字符回退会导致同行字重与基线不一致;
  2. 静态子集字体上的 font-weight 是无效声明——浏览器就近取档且只合成粗体,需要更细字重必须引入对应字面。

灌水


灌水