611 lines
14 KiB
Markdown
611 lines
14 KiB
Markdown
# 方案1-第2步:字体子集化优化 - 实用指南
|
||
|
||
## 🎯 优化目标
|
||
|
||
将自定义中文字体从 **~1.5MB** 减少到 **~200KB**,提升 80%+
|
||
|
||
---
|
||
|
||
## 📋 前置准备
|
||
|
||
### 检查当前字体文件
|
||
|
||
```bash
|
||
# 查看字体文件大小
|
||
ls -lh themes/Ying/static/font/zql-v2.woff2
|
||
ls -lh themes/Ying/static/font/zql-v2.woff
|
||
|
||
# 预期大小:800KB - 2MB(包含完整中文字符集)
|
||
```
|
||
|
||
### 备份原始字体文件
|
||
|
||
```bash
|
||
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(推荐)
|
||
|
||
```bash
|
||
# 全局安装
|
||
npm install -g glyphhanger
|
||
|
||
# 验证安装
|
||
glyphhanger --version
|
||
```
|
||
|
||
### 方法2:使用fonttools(Python)
|
||
|
||
```bash
|
||
# 安装Python工具
|
||
pip install fonttools brotli
|
||
|
||
# 验证安装
|
||
python3 -c "from fontTools.ttLib import TTFont; print('fonttools installed')"
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 实施步骤
|
||
|
||
### 步骤1:构建Hugo站点
|
||
|
||
```bash
|
||
cd E:\GitHub\blog
|
||
|
||
# 构建静态文件
|
||
hugo --destination=public
|
||
|
||
# 确认构建成功
|
||
ls -la public/
|
||
```
|
||
|
||
### 步骤2:运行字体子集化
|
||
|
||
#### 方法1:使用glyphhanger(简单)
|
||
|
||
```bash
|
||
# 从本地文件扫描并生成子集
|
||
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`
|
||
|
||
```python
|
||
#!/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()
|
||
```
|
||
|
||
运行脚本:
|
||
|
||
```bash
|
||
# 确保已构建Hugo
|
||
hugo --destination=public
|
||
|
||
# 运行字体子集化脚本
|
||
python3 scripts/subset-font.py
|
||
```
|
||
|
||
### 步骤3:验证生成的子集字体
|
||
|
||
```bash
|
||
# 检查生成的文件
|
||
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行的字体声明:**
|
||
|
||
```css
|
||
@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;
|
||
/* 中文标点符号 */
|
||
}
|
||
```
|
||
|
||
**替换为:**
|
||
|
||
```css
|
||
@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
|
||
|
||
```bash
|
||
# 清理缓存
|
||
hugo --cleanDestinationDir
|
||
|
||
# 重新构建
|
||
hugo --destination=public
|
||
|
||
# 启动开发服务器
|
||
hugo server -D
|
||
```
|
||
|
||
### 步骤2:检查字体加载
|
||
|
||
#### Network面板验证
|
||
|
||
1. 打开Chrome DevTools(F12)
|
||
2. 切换到 **Network** 面板
|
||
3. 刷新页面
|
||
4. 查找字体文件(.woff2, .woff)
|
||
|
||
**预期结果:**
|
||
- ✅ 看到 `zql-v2-subset.woff2` 加载
|
||
- ✅ 文件大小显著减少(<300KB)
|
||
- ✅ 无404错误
|
||
|
||
#### 视觉检查
|
||
|
||
**中文字符测试:**
|
||
- [ ] 常用汉字正常(你好世界、欢迎访问)
|
||
- [ ] 文章内容中的中文正常
|
||
- [ ] 导航菜单中文正常
|
||
- [ ] 深色模式下中文正常
|
||
|
||
**英文字符测试:**
|
||
- [ ] 英文字母正常(A-Z, a-z)
|
||
- [ ] 数字正常(0-9)
|
||
- [ ] 常用符号正常(@#$%)
|
||
|
||
**特殊字符测试:**
|
||
- [ ] 中文标点正常(,。!?、;:)
|
||
- [ ] 英文标点正常(,.!?;:)
|
||
- [ ] 其他符号正常(如果文章中有)
|
||
|
||
### 步骤3:不同页面测试
|
||
|
||
- [ ] 首页字体正常
|
||
- [ ] 文章详情页字体正常
|
||
- [ ] 友链页面字体正常
|
||
- [ ] 归档页面字体正常
|
||
- [ ] 移动端字体正常
|
||
|
||
### 步骤4:Lighthouse测试
|
||
|
||
```bash
|
||
# 运行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:扩大字符范围
|
||
|
||
```bash
|
||
# 修改Python脚本,扫描更多目录
|
||
scan_dirs = ["public", "content", "layouts", "static"]
|
||
|
||
# 或者添加常用字符
|
||
common_chars = "的一是不了人我在有他这为之大来以个中上们到说国和地也子时"
|
||
chars.update(set(common_chars))
|
||
```
|
||
|
||
#### 方案B:使用更大的字符集
|
||
|
||
```bash
|
||
# 下载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
|
||
|
||
```css
|
||
/* 在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路径不正确或文件未生成
|
||
|
||
**解决方案:**
|
||
|
||
```bash
|
||
# 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:子集化后文件仍然很大
|
||
|
||
**症状:** 生成的子集字体大小与原始差不多
|
||
|
||
**原因:** 可能扫描了太多字符
|
||
|
||
**解决方案:**
|
||
|
||
```bash
|
||
# 查看提取了多少字符
|
||
wc -c themes/Ying/static/font/used_chars.txt
|
||
|
||
# 如果字符数 > 5000,说明范围太广
|
||
# 修改脚本,只提取实际使用的字符
|
||
```
|
||
|
||
### 问题4:Python脚本运行失败
|
||
|
||
**症状:** 报错缺少依赖或路径错误
|
||
|
||
**解决方案:**
|
||
|
||
```bash
|
||
# 确保安装了所有依赖
|
||
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/
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 回滚方案
|
||
|
||
如果字体优化后出现问题,立即回滚:
|
||
|
||
```bash
|
||
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. 定期更新子集字体
|
||
|
||
每当网站内容大幅更新时,重新运行子集化:
|
||
|
||
```bash
|
||
# 每月或每季度运行一次
|
||
cd E:\GitHub\blog
|
||
hugo --destination=public
|
||
python3 scripts/subset-font.py
|
||
```
|
||
|
||
### 2. 监控字体大小
|
||
|
||
在部署前检查字体文件大小:
|
||
|
||
```bash
|
||
# 如果字体 > 500KB,可能需要优化
|
||
ls -lh themes/Ying/static/font/zql-v2-subset.woff2
|
||
```
|
||
|
||
### 3. 使用CDN缓存
|
||
|
||
确保字体文件通过CDN分发:
|
||
|
||
```nginx
|
||
# Nginx配置
|
||
location ~* \.(woff2|woff)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎉 优化完成
|
||
|
||
完成字体子集化后,你的网站将获得:
|
||
|
||
- ⚡ **字体大小减少80%+**
|
||
- ⚡ **加载速度提升80%+**
|
||
- ⚡ **移动端体验显著提升**
|
||
- ⚡ **用户流量节省80%+**
|
||
|
||
结合JS优化,总体性能提升约 **40-50%**!
|
||
|
||
---
|
||
|
||
**文档版本:** v1.0
|
||
**创建时间:** 2026-06-03
|
||
**适用方案:** 方案1(保守优化)
|