14 KiB
14 KiB
方案1-第2步:字体子集化优化 - 实用指南
🎯 优化目标
将自定义中文字体从 ~1.5MB 减少到 ~200KB,提升 80%+
📋 前置准备
检查当前字体文件
# 查看字体文件大小
ls -lh themes/Ying/static/font/zql-v2.woff2
ls -lh themes/Ying/static/font/zql-v2.woff
# 预期大小:800KB - 2MB(包含完整中文字符集)
备份原始字体文件
cd E:\GitHub\blog
# 创建备份
cp themes/Ying/static/font/zql-v2.woff2 themes/Ying/static/font/zql-v2.woff2.backup
cp themes/Ying/static/font/zql-v2.woff themes/Ying/static/font/zql-v2.woff.backup
# 确认备份成功
ls -lh themes/Ying/static/font/*.backup
🔧 安装工具
方法1:使用glyphhanger(推荐)
# 全局安装
npm install -g glyphhanger
# 验证安装
glyphhanger --version
方法2:使用fonttools(Python)
# 安装Python工具
pip install fonttools brotli
# 验证安装
python3 -c "from fontTools.ttLib import TTFont; print('fonttools installed')"
🚀 实施步骤
步骤1:构建Hugo站点
cd E:\GitHub\blog
# 构建静态文件
hugo --destination=public
# 确认构建成功
ls -la public/
步骤2:运行字体子集化
方法1:使用glyphhanger(简单)
# 从本地文件扫描并生成子集
glyphhanger \
--subset=themes/Ying/static/font/zql-v2.woff2 \
--formats=woff2,woff \
--CSS=public/css/*.css \
--HTML=public/**/*.html \
--output=themes/Ying/static/font/
# 这会生成:
# - zql-v2-subset.woff2(子集字体)
# - zql-v2-subset.woff(子集字体降级格式)
方法2:使用Python脚本(更灵活)
创建文件:scripts/subset-font.py
#!/usr/bin/env python3
"""
字体子集化脚本
从HTML和CSS文件中提取使用的字符,生成子集字体
"""
import os
import re
import sys
from fontTools.ttLib import TTFont
from fontTools.subset import Subsetter, Options
def extract_chars_from_files(directories):
"""从文件中提取使用的字符"""
chars = set()
for directory in directories:
if not os.path.exists(directory):
print(f"⚠️ 目录不存在: {directory}")
continue
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(('.html', '.md', '.css')):
filepath = os.path.join(root, file)
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# 提取中文字符
chinese_chars = re.findall(r'[一-鿿]', content)
chars.update(chinese_chars)
# 提取中文标点
cjk_punct = re.findall(r'[ -〿-]', content)
chars.update(cjk_punct)
# 提取英文和数字
ascii_chars = re.findall(r'[a-zA-Z0-9]', content)
chars.update(ascii_chars)
# 提取常用英文标点
en_punct = re.findall(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', content)
chars.update(en_punct)
except Exception as e:
print(f"⚠️ 读取文件失败 {filepath}: {e}")
return chars
def subset_font(input_font_path, output_font_path, chars):
"""生成子集字体"""
print(f"📦 加载字体: {input_font_path}")
# 加载原始字体
font = TTFont(input_font_path)
# 获取原始字符数
cmap = font.getBestCmap()
original_count = len(cmap) if cmap else 0
# 配置子集化选项
options = Options()
if output_font_path.endswith('.woff2'):
options.flavor = 'woff2'
elif output_font_path.endswith('.woff'):
options.flavor = 'woff'
options.desubroutinize = True
# 创建子集化器
subsetter = Subsetter(options=options)
subsetter.populate(text=''.join(chars))
# 执行子集化
print("✂️ 正在生成子集字体...")
subsetter.subset(font)
# 保存子集字体
font.save(output_font_path)
# 获取子集字符数
subset_font = TTFont(output_font_path)
subset_cmap = subset_font.getBestCmap()
subset_count = len(subset_cmap) if subset_cmap else 0
# 计算大小
original_size = os.path.getsize(input_font_path)
subset_size = os.path.getsize(output_font_path)
reduction = original_size - subset_size
percentage = (reduction / original_size) * 100 if original_size > 0 else 0
print(f"\n✅ 子集化完成!")
print(f"📊 统计信息:")
print(f" 原始字符数: {original_count}")
print(f" 子集字符数: {subset_count}")
print(f" 原始大小: {original_size / 1024:.1f} KB")
print(f" 子集大小: {subset_size / 1024:.1f} KB")
print(f" 减少: {reduction / 1024:.1f} KB ({percentage:.1f}%)")
def main():
# 配置路径
font_dir = "themes/Ying/static/font"
input_font = os.path.join(font_dir, "zql-v2.woff2")
# 检查输入文件
if not os.path.exists(input_font):
print(f"❌ 字体文件不存在: {input_font}")
sys.exit(1)
# 扫描目录
scan_dirs = ["public", "content", "layouts"]
print(f"🔍 扫描目录: {', '.join(scan_dirs)}")
# 提取字符
chars = extract_chars_from_files(scan_dirs)
print(f"📝 提取了 {len(chars)} 个唯一字符")
if len(chars) == 0:
print("❌ 未找到任何字符,请检查扫描目录")
sys.exit(1)
# 保存字符列表(用于调试)
chars_file = os.path.join(font_dir, "used_chars.txt")
with open(chars_file, 'w', encoding='utf-8') as f:
f.write(''.join(sorted(chars)))
print(f"💾 字符列表已保存到: {chars_file}")
# 生成woff2子集字体
output_woff2 = os.path.join(font_dir, "zql-v2-subset.woff2")
subset_font(input_font, output_woff2, chars)
# 生成woff子集字体
output_woff = os.path.join(font_dir, "zql-v2-subset.woff")
subset_font(input_font, output_woff, chars)
print(f"\n🎉 所有子集字体生成完成!")
print(f"📂 文件位置: {font_dir}")
if __name__ == "__main__":
main()
运行脚本:
# 确保已构建Hugo
hugo --destination=public
# 运行字体子集化脚本
python3 scripts/subset-font.py
步骤3:验证生成的子集字体
# 检查生成的文件
ls -lh themes/Ying/static/font/zql-v2-subset.*
# 预期大小:
# zql-v2-subset.woff2: 100-300KB(比原始减少80%+)
# zql-v2-subset.woff: 150-400KB
📝 更新CSS字体声明
修改main.css
文件: themes/Ying/assets/css/main.css
找到第1-14行的字体声明:
@font-face {
font-family: 'zql';
src: url('../font/zql-v2.woff2') format('woff2'),
url('../font/zql-v2.woff') format('woff');
font-display: swap;
unicode-range: U+0000-007F,
/* 基本拉丁字母 */
U+4E00-9FFF,
/* 基本汉字 */
U+2000-206F,
/* 通用标点符号 */
U+3000-303F;
/* 中文标点符号 */
}
替换为:
@font-face {
font-family: 'zql';
src: url('../font/zql-v2-subset.woff2') format('woff2'),
url('../font/zql-v2-subset.woff') format('woff');
font-display: swap;
}
/* 可选:保留原始字体作为fallback(如果子集遗漏字符) */
/*
@font-face {
font-family: 'zql-full';
src: url('../font/zql-v2.woff2') format('woff2'),
url('../font/zql-v2.woff') format('woff');
font-display: swap;
unicode-range: U+0000-007F, U+4E00-9FFF, U+2000-206F, U+3000-303F;
}
body {
font-family: 'zql', 'zql-full', serif;
}
*/
🧪 测试验证
步骤1:重新构建Hugo
# 清理缓存
hugo --cleanDestinationDir
# 重新构建
hugo --destination=public
# 启动开发服务器
hugo server -D
步骤2:检查字体加载
Network面板验证
- 打开Chrome DevTools(F12)
- 切换到 Network 面板
- 刷新页面
- 查找字体文件(.woff2, .woff)
预期结果:
- ✅ 看到
zql-v2-subset.woff2加载 - ✅ 文件大小显著减少(<300KB)
- ✅ 无404错误
视觉检查
中文字符测试:
- 常用汉字正常(你好世界、欢迎访问)
- 文章内容中的中文正常
- 导航菜单中文正常
- 深色模式下中文正常
英文字符测试:
- 英文字母正常(A-Z, a-z)
- 数字正常(0-9)
- 常用符号正常(@#$%)
特殊字符测试:
- 中文标点正常(,。!?、;:)
- 英文标点正常(,.!?;:)
- 其他符号正常(如果文章中有)
步骤3:不同页面测试
- 首页字体正常
- 文章详情页字体正常
- 友链页面字体正常
- 归档页面字体正常
- 移动端字体正常
步骤4:Lighthouse测试
# 运行Lighthouse审计
# 预期:Performance得分提升,无字体相关警告
📊 预期优化效果
文件大小对比
| 文件 | 优化前 | 优化后 | 减少 |
|---|---|---|---|
| zql-v2.woff2 | 1.2MB | 150KB | ⚡ -87% |
| zql-v2.woff | 1.5MB | 200KB | ⚡ -87% |
加载时间提升
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 字体下载(3G) | ~3s | ~0.5s | ⚡ -83% |
| FCP | +0.5s | +0.1s | ⚡ -80% |
| 字体加载总时间 | ~4s | ~1s | ⚡ -75% |
🐛 故障排除
问题1:字符显示为方块(□)
症状: 部分中文字符显示为方块
原因: 子集字体未包含该字符
解决方案:
方案A:扩大字符范围
# 修改Python脚本,扫描更多目录
scan_dirs = ["public", "content", "layouts", "static"]
# 或者添加常用字符
common_chars = "的一是不了人我在有他这为之大来以个中上们到说国和地也子时"
chars.update(set(common_chars))
方案B:使用更大的字符集
# 下载GB2312常用字符列表(6,763个字符)
# 手动添加到字符集
# 或者使用CJK字符范围
import unicodedata
cjk_chars = [chr(i) for i in range(0x4E00, 0x9FFF+1) if unicodedata.category(chr(i)) != 'Cn']
chars.update(cjk_chars)
方案C:保留原始字体作为fallback
/* 在main.css中使用font-family fallback */
@font-face {
font-family: 'zql-subset';
src: url('../font/zql-v2-subset.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'zql-full';
src: url('../font/zql-v2.woff2') format('woff2');
font-display: swap;
unicode-range: U+4E00-9FFF; /* 仅CJK字符 */
}
body {
font-family: 'zql-subset', 'zql-full', serif;
}
问题2:字体文件404错误
症状: 浏览器Console显示字体文件404
原因: CSS路径不正确或文件未生成
解决方案:
# 1. 检查文件是否存在
ls -lh themes/Ying/static/font/zql-v2-subset.*
# 2. 确认CSS路径正确
grep -n "zql-v2-subset" themes/Ying/assets/css/main.css
# 3. 重新构建Hugo
hugo --cleanDestinationDir
问题3:子集化后文件仍然很大
症状: 生成的子集字体大小与原始差不多
原因: 可能扫描了太多字符
解决方案:
# 查看提取了多少字符
wc -c themes/Ying/static/font/used_chars.txt
# 如果字符数 > 5000,说明范围太广
# 修改脚本,只提取实际使用的字符
问题4:Python脚本运行失败
症状: 报错缺少依赖或路径错误
解决方案:
# 确保安装了所有依赖
pip install fonttools brotli
# 确保在正确的目录运行
cd E:\GitHub\blog
python3 scripts/subset-font.py
# 如果仍然失败,使用glyphhanger替代
npm install -g glyphhanger
glyphhanger --subset=themes/Ying/static/font/zql-v2.woff2 --formats=woff2,woff --CSS=public/css/*.css --HTML=public/**/*.html --output=themes/Ying/static/font/
🔄 回滚方案
如果字体优化后出现问题,立即回滚:
cd E:\GitHub\blog
# 恢复原始字体文件
cp themes/Ying/static/font/zql-v2.woff2.backup themes/Ying/static/font/zql-v2.woff2
cp themes/Ying/static/font/zql-v2.woff.backup themes/Ying/static/font/zql-v2.woff
# 删除子集字体(可选)
rm themes/Ying/static/font/zql-v2-subset.*
# 恢复CSS字体声明
# 将 main.css 中的字体路径改回原始文件名:
# zql-v2-subset.woff2 → zql-v2.woff2
# zql-v2-subset.woff → zql-v2.woff
✅ 完成确认清单
文件生成
zql-v2-subset.woff2已生成(<300KB)zql-v2-subset.woff已生成(<400KB)used_chars.txt已生成(用于调试)
CSS更新
main.css字体声明已更新- 指向新的子集字体文件
测试验证
- 中文字符显示正常
- 英文字符显示正常
- 特殊符号显示正常
- 所有页面字体正常
- 深色模式字体正常
- 移动端字体正常
- 无Console错误
性能验证
- Network面板显示字体大小减少
- Lighthouse无字体相关警告
- FCP指标改善
📈 最佳实践
1. 定期更新子集字体
每当网站内容大幅更新时,重新运行子集化:
# 每月或每季度运行一次
cd E:\GitHub\blog
hugo --destination=public
python3 scripts/subset-font.py
2. 监控字体大小
在部署前检查字体文件大小:
# 如果字体 > 500KB,可能需要优化
ls -lh themes/Ying/static/font/zql-v2-subset.woff2
3. 使用CDN缓存
确保字体文件通过CDN分发:
# Nginx配置
location ~* \.(woff2|woff)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
🎉 优化完成
完成字体子集化后,你的网站将获得:
- ⚡ 字体大小减少80%+
- ⚡ 加载速度提升80%+
- ⚡ 移动端体验显著提升
- ⚡ 用户流量节省80%+
结合JS优化,总体性能提升约 40-50%!
文档版本: v1.0 创建时间: 2026-06-03 适用方案: 方案1(保守优化)