前端 ttf / woff2 字体资源完整优化方案
前端 ttf / woff2 字体资源完整优化方案
一、核心基础认知
- 字体格式优先级
woff2 > woff > ttf > otf
• woff2:压缩率最高(比ttf小40%~70%),现代浏览器全支持,生产首选
• ttf:无压缩,体积巨大,仅作兼容兜底,线上不建议直接加载
- 浏览器支持
woff2:Chrome/Firefox/Safari/Edge 全部支持,仅极老设备不兼容,可忽略。
二、第一步:字体精简(最关键,大幅瘦身)
ttf 原生包含几万汉字、多余符号、多字重,必须裁剪只保留项目用到的字符。
方案1:FontTools(Python,批量自动化)
安装
pip install fonttools
裁剪命令(只保留指定文字)
保留字符:常用汉字+字母数字+标点
pyftsubset source.ttf
–text=“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,。!?一二三四五六七八九十你好项目页面标题内容”
–flavor=woff2
–output=font-cut.woff2
参数说明:
• --text:写页面所有用到的文字,只保留这些字形
• --flavor=woff2:直接输出woff2,无需二次压缩
• --unicodes:大批量生僻字可用Unicode范围代替text
方案2:在线工具(无需安装)
- Font Squirrel Webfont Generator(推荐)
上传ttf → 勾选 Expert
• Subset:填入用到的字符
• Formats:只勾选 WOFF2
下载后直接拿到裁剪好的woff2。
- Transfonter:同样支持字符子集、导出woff2。
方案3:工具字重合并
多字重(常规/粗体)分开裁剪,不要打包在一个字体文件,按需拆分。
三、第二步:字体文件格式转换 & 压缩
禁止直接引入ttf到线上,ttf仅作为源文件
转换规则:
• 开发源文件:ttf/otf
• 线上产物:仅 woff2
- 压缩对比示例
原生ttf中文字体:8~15MB
裁剪后woff2:几十KB ~ 300KB
四、第三步:CSS 加载优化(性能核心)
- @font-face 标准最简写法
@font-face {
font-family: “CustomFont”;
src: url(“/fonts/font.woff2”) format(“woff2”);
font-weight: 400;
font-style: normal;
/* 关键优化属性/
font-display: swap; /无FOIT阻塞,推荐/
/font-display: fallback; 追求极致CLS用 */
}
font-display 区别
• swap:先显示系统默认字体,字体下载完成后替换,无白框阻塞,业务首选
• block:文字空白等待字体,不推荐
• fallback:短时间空白,超时永久使用系统字体,减少布局偏移CLS
- 按需拆分字体,分文件加载
不要把全部字重塞进一个文件:
/* 常规字重/
@font-face {
font-family: “CustomFont”;
src: url(“/fonts/font-400.woff2”) format(“woff2”);
font-weight: 400;
}
/粗体单独文件 */
@font-face {
font-family: “CustomFont”;
src: url(“/fonts/font-700.woff2”) format(“woff2”);
font-weight: 700;
}
页面只用400就不会加载700大文件。
- 控制字体加载范围
• 仅中文页面加载中文字体,英文页面只用系统无衬线字体
• 小图标、数字不要依赖自定义字体,改用SVG图标
五、第四步:网络传输优化(静态资源层面)
- 开启服务器压缩
woff2本身已是压缩格式,不要再开gzip/br二次压缩,会反向变大。
Nginx 配置示例:
location ~* .(woff2)$ {
root /static;
expires 30d; /* 长期缓存 */
add_header Cache-Control “public, max-age=2592000”;
关闭gzip
gzip off;
}
2. 强缓存策略
字体文件极少改动,设置30天+缓存,文件名加hash防更新缓存:
font.abc123.woff2
构建工具(Vite/Webpack)打包自动生成hash后缀。
- CDN 分发
字体放静态CDN,并行加载,避免同域名资源排队。
六、第五步:构建工具打包优化(Vite/Webpack)
Vite 内置优化
配置 assets 分离字体到独立目录
// vite.config.js
export default defineConfig({
build: {
assetsDir: “fonts”,
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
if (assetInfo.name.endsWith(“.woff2”)) {
return “fonts/[name]-[hash][extname]”;
}
return “assets/[name]-[hash][extname]”;
},
},
},
},
});禁止内联字体:Vite默认小于4kb转base64,字体不要内联,会增大JS/CSS体积
export default defineConfig({
assetsInclude: [],
build: {
chunkSizeWarningLimit: 1000,
},
});
Webpack 处理字体
module.exports = {
module: {
rules: [
{
test: /.(woff2)$/i,
type: “asset/resource”,
generator: {
filename: “fonts/[name].[hash][ext]”,
},
parser: {
dataUrlCondition: {
maxSize: 0, // 全部输出文件,不转base64
},
},
},
],
},
};
七、第六步:性能兜底策略(避免阻塞、布局抖动)预加载关键字体
首页核心字体提前请求,放到
- 降级方案:优先使用系统字体兜底
body {
font-family: “CustomFont”, -apple-system, BlinkMacSystemFont, “Microsoft YaHei”, sans-serif;
} - 延迟非关键字体
弹窗、次要标题用到的字体,不preload,等页面渲染完成再加载。
八、特殊场景优化:大字符集(后台/生僻字)
- 拆分字体包:
• 常用字包(首页基础文字,100KB内)
• 生僻字分包(弹窗/详情页按需动态加载)
JS动态加载字体(分包懒加载)
async function loadFont() {
const font = new FontFace(“CustomFont-Big”, “url(/fonts/big.woff2) format(‘woff2’)”);
await font.load();
document.fonts.add(font);
document.body.classList.add(“loaded-big-font”);
}
// 滚动到对应区域再调用
九、避坑清单❌ 线上直接引入ttf,体积巨大
❌ 不做字符子集,完整中文字体十几MB
❌ 多个字重打包进一个文件,浪费流量
❌ 开启gzip压缩woff2,文件变大
❌ 不加font-display,页面文字空白卡顿
❌ 小字体转base64内嵌CSS,增大首屏CSS体积
❌ 不设置缓存,每次刷新重复下载字体
十、完整优化流程总结
源文件ttf → 裁剪子集(只保留项目文字)
导出仅 woff2 格式,拆分多字重独立文件
CSS 使用 font-display: swap,合理 fallback
HTML preload 关键字体,懒加载次要字体
构建打包分离字体、加hash文件名
Nginx/CDN 关闭gzip,设置30天强缓存
复杂页面做字体分包动态加载
