610 lines
15 KiB
Markdown
610 lines
15 KiB
Markdown
# 第3步:字体子集化优化 - 实施方案
|
||
|
||
## 📋 当前字体分析
|
||
|
||
### 字体文件清单
|
||
|
||
#### 1. zql-v2.woff2(自定义中文字体)
|
||
**位置:** `themes/Ying/static/font/zql-v2.woff2`
|
||
**当前大小:** 约 800KB - 2MB(包含完整中文字符集)
|
||
**用途:** 正文显示(body默认字体)
|
||
|
||
**问题:**
|
||
- 包含全部中文字符(20,000+)
|
||
- 实际使用的字符可能只有500-2000个
|
||
- 大量未使用的字符浪费带宽
|
||
|
||
#### 2. zql-v2.woff(降级格式)
|
||
**位置:** `themes/Ying/static/font/zql-v2.woff`
|
||
**当前大小:** 与woff2类似
|
||
**用途:** 不支持woff2的浏览器降级
|
||
|
||
#### 3. remixicon.woff2/woff(图标字体)
|
||
**位置:** `themes/Ying/static/font/remixicon.*`
|
||
**大小:** 约 100-200KB
|
||
**用途:** UI图标(搜索、主题切换等)
|
||
|
||
**注意:** 图标字体通常不建议子集化,因为图标字符集是固定的
|
||
|
||
---
|
||
|
||
## 🎯 优化策略
|
||
|
||
### 策略1:自动字符提取(推荐)
|
||
|
||
使用工具自动扫描HTML文件,提取实际使用的字符,生成子集字体。
|
||
|
||
**优点:**
|
||
- 全自动化,无需手动维护
|
||
- 准确提取所有使用的字符
|
||
- 可集成到构建流程
|
||
|
||
**工具选项:**
|
||
1. **glyphhanger** - Google推荐
|
||
2. **fonttools** - Python工具
|
||
3. **subfont** - 基于PostCSS
|
||
|
||
### 策略2:手动字符列表
|
||
|
||
手动列出常用的中文字符(GB2312常用字约6,763个)
|
||
|
||
**优点:**
|
||
- 字符集稳定,不会遗漏
|
||
- 适合内容变化不大的网站
|
||
|
||
**缺点:**
|
||
- 可能包含未使用的字符
|
||
- 需要手动维护
|
||
|
||
---
|
||
|
||
## 📝 实施方案:策略1(自动字符提取)
|
||
|
||
### 步骤3.1:安装glyphhanger
|
||
|
||
```bash
|
||
# 使用npm全局安装
|
||
npm install -g glyphhanger
|
||
|
||
# 或者使用pip安装fonttools
|
||
pip install fonttools brotli
|
||
|
||
# 或者使用subfont
|
||
npm install -g subfont
|
||
```
|
||
|
||
### 步骤3.2:扫描网站提取字符
|
||
|
||
#### 方法1:从本地构建文件扫描
|
||
|
||
```bash
|
||
# 先构建Hugo站点
|
||
cd E:\GitHub\blog
|
||
hugo
|
||
|
||
# 使用glyphhanger扫描public目录
|
||
glyphhanger --subset=themes/Ying/static/font/zql-v2.woff2 \
|
||
--formats=woff2 \
|
||
--CSS=public/css/*.css \
|
||
--HTML=public/**/*.html \
|
||
--output=themes/Ying/static/font/
|
||
```
|
||
|
||
#### 方法2:从在线网站扫描(推荐)
|
||
|
||
```bash
|
||
# 扫描在线网站(需要先部署或本地预览)
|
||
glyphhanger https://your-blog-url.com \
|
||
--subset=themes/Ying/static/font/zql-v2.woff2 \
|
||
--formats=woff2 \
|
||
--output=themes/Ying/static/font/
|
||
```
|
||
|
||
#### 方法3:使用fonttools手动提取
|
||
|
||
```bash
|
||
# 1. 提取所有使用的字符到文本文件
|
||
python3 << 'EOF'
|
||
import os
|
||
import re
|
||
|
||
def extract_chars_from_html(directory):
|
||
chars = set()
|
||
for root, dirs, files in os.walk(directory):
|
||
for file in files:
|
||
if file.endswith('.html') or file.endswith('.md'):
|
||
filepath = os.path.join(root, file)
|
||
with open(filepath, 'r', encoding='utf-8') as f:
|
||
content = f.read()
|
||
# 提取中文字符
|
||
chinese_chars = re.findall(r'[一-鿿]', content)
|
||
chars.update(chinese_chars)
|
||
# 提取英文和数字
|
||
ascii_chars = re.findall(r'[a-zA-Z0-9]', content)
|
||
chars.update(ascii_chars)
|
||
# 提取常用标点
|
||
punctuation = re.findall(r'[,。!?、;:""''()【】《》]', content)
|
||
chars.update(punctuation)
|
||
return chars
|
||
|
||
# 扫描content和layouts目录
|
||
content_chars = extract_chars_from_html('content')
|
||
layout_chars = extract_chars_from_html('layouts')
|
||
all_chars = content_chars | layout_chars
|
||
|
||
# 保存到文件
|
||
with open('used_chars.txt', 'w', encoding='utf-8') as f:
|
||
f.write(''.join(sorted(all_chars)))
|
||
|
||
print(f"提取了 {len(all_chars)} 个字符")
|
||
EOF
|
||
|
||
# 2. 使用fonttools生成子集字体
|
||
pip install fonttools brotli
|
||
|
||
python3 << 'EOF'
|
||
from fontTools.ttLib import TTFont
|
||
from fontTools.subset import Subsetter, Options
|
||
|
||
# 读取使用的字符
|
||
with open('used_chars.txt', 'r', encoding='utf-8') as f:
|
||
used_chars = set(f.read())
|
||
|
||
# 加载原始字体
|
||
font = TTFont('themes/Ying/static/font/zql-v2.woff2')
|
||
|
||
# 配置子集化选项
|
||
options = Options()
|
||
options.flavor = 'woff2'
|
||
options.desubroutinize = True
|
||
|
||
# 创建子集化器
|
||
subsetter = Subsetter(options=options)
|
||
subsetter.populate(text=''.join(used_chars))
|
||
|
||
# 执行子集化
|
||
subsetter.subset(font)
|
||
|
||
# 保存子集字体
|
||
font.save('themes/Ying/static/font/zql-v2-subset.woff2')
|
||
|
||
print("子集字体生成完成!")
|
||
EOF
|
||
```
|
||
|
||
### 步骤3.3:更新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;
|
||
/* 不再需要unicode-range,因为已经是子集 */
|
||
}
|
||
|
||
/* 保留原始字体作为fallback(可选) */
|
||
@font-face {
|
||
font-family: 'zql-fallback';
|
||
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-fallback', serif;
|
||
}
|
||
```
|
||
|
||
### 步骤3.4:同步更新critical.css
|
||
|
||
**修改文件:** `themes/Ying/assets/css/critical.css`
|
||
|
||
**当前代码(第16-23行):**
|
||
```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;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 自动化脚本
|
||
|
||
### 创建构建脚本
|
||
|
||
**文件路径:** `themes/Ying/scripts/subset-fonts.sh`
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# 字体子集化自动化脚本
|
||
# 用法:./scripts/subset-fonts.sh [online|local]
|
||
|
||
set -e
|
||
|
||
MODE=${1:-local}
|
||
BLOG_URL=${2:-http://localhost:1313}
|
||
FONT_DIR="themes/Ying/static/font"
|
||
ORIGINAL_FONT="$FONT_DIR/zql-v2.woff2"
|
||
SUBSET_FONT="$FONT_DIR/zql-v2-subset.woff2"
|
||
|
||
echo "🔤 开始字体子集化..."
|
||
echo "模式: $MODE"
|
||
|
||
# 检查依赖
|
||
if ! command -v glyphhanger &> /dev/null; then
|
||
echo "❌ glyphhanger未安装,正在安装..."
|
||
npm install -g glyphhanger
|
||
fi
|
||
|
||
# 备份原始字体
|
||
if [ ! -f "$ORIGINAL_FONT.bak" ]; then
|
||
cp "$ORIGINAL_FONT" "$ORIGINAL_FONT.bak"
|
||
echo "✅ 原始字体已备份到 $ORIGINAL_FONT.bak"
|
||
fi
|
||
|
||
if [ "$MODE" = "online" ]; then
|
||
echo "📡 从在线网站扫描字符..."
|
||
glyphhanger "$BLOG_URL" \
|
||
--subset="$ORIGINAL_FONT" \
|
||
--formats=woff2,woff \
|
||
--output="$FONT_DIR/"
|
||
else
|
||
echo "📁 从本地文件扫描字符..."
|
||
|
||
# 先构建Hugo
|
||
echo "🔨 构建Hugo站点..."
|
||
hugo --destination=public_temp
|
||
|
||
# 扫描HTML和CSS
|
||
glyphhanger \
|
||
--subset="$ORIGINAL_FONT" \
|
||
--formats=woff2,woff \
|
||
--CSS=public_temp/css/*.css \
|
||
--HTML=public_temp/**/*.html \
|
||
--output="$FONT_DIR/"
|
||
|
||
# 清理临时目录
|
||
rm -rf public_temp
|
||
fi
|
||
|
||
# 检查生成的文件
|
||
if [ -f "$SUBSET_FONT" ]; then
|
||
ORIGINAL_SIZE=$(stat -f%z "$ORIGINAL_FONT" 2>/dev/null || stat -c%s "$ORIGINAL_FONT")
|
||
SUBSET_SIZE=$(stat -f%z "$SUBSET_FONT" 2>/dev/null || stat -c%s "$SUBSET_FONT")
|
||
REDUCTION=$((ORIGINAL_SIZE - SUBSET_SIZE))
|
||
PERCENTAGE=$((REDUCTION * 100 / ORIGINAL_SIZE))
|
||
|
||
echo ""
|
||
echo "✅ 字体子集化完成!"
|
||
echo "📊 大小对比:"
|
||
echo " 原始字体: $(numfmt --to=iec $ORIGINAL_SIZE)"
|
||
echo " 子集字体: $(numfmt --to=iec $SUBSET_SIZE)"
|
||
echo " 减少: $(numfmt --to=iec $REDUCTION) ($PERCENTAGE%)"
|
||
echo ""
|
||
echo "📝 下一步:"
|
||
echo " 1. 更新CSS中的字体路径为 zql-v2-subset.woff2"
|
||
echo " 2. 测试网站显示是否正常"
|
||
echo " 3. 提交代码"
|
||
else
|
||
echo "❌ 子集字体生成失败"
|
||
exit 1
|
||
fi
|
||
```
|
||
|
||
### 设置执行权限
|
||
|
||
```bash
|
||
chmod +x themes/Ying/scripts/subset-fonts.sh
|
||
```
|
||
|
||
### 使用方法
|
||
|
||
```bash
|
||
# 方法1:从本地文件扫描(推荐开发时使用)
|
||
./themes/Ying/scripts/subset-fonts.sh local
|
||
|
||
# 方法2:从在线网站扫描(推荐部署前使用)
|
||
./themes/Ying/scripts/subset-fonts.sh online https://your-blog.com
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 预期收益
|
||
|
||
### 文件大小对比(预估)
|
||
|
||
| 字体文件 | 优化前 | 优化后 | 减少 |
|
||
|---------|--------|--------|------|
|
||
| **zql-v2.woff2** | 1.2MB | 150KB | ⚡ -87% |
|
||
| **zql-v2.woff** | 1.5MB | 200KB | ⚡ -87% |
|
||
|
||
### 加载时间提升
|
||
|
||
| 指标 | 优化前 | 优化后 | 提升 |
|
||
|------|--------|--------|------|
|
||
| **字体下载时间** | ~3s (3G) | ~0.5s (3G) | ⚡ -83% |
|
||
| **FCP** | +0.5s | +0.1s | ⚡ -80% |
|
||
| **FOIT时间** | 长 | 短 | ⚡ 显著改善 |
|
||
|
||
### 用户体验提升
|
||
|
||
- ✅ 字体加载更快
|
||
- ✅ 文字闪烁减少(FOIT/FOUT)
|
||
- ✅ 移动端体验更好(节省流量)
|
||
- ✅ 首屏渲染更快
|
||
|
||
---
|
||
|
||
## 🧪 测试验证清单
|
||
|
||
### 功能测试
|
||
|
||
#### 1. 字体显示测试
|
||
- [ ] 中文字符正确显示
|
||
- [ ] 常用汉字(你好世界)
|
||
- [ ] 生僻字(如果文章中有)
|
||
- [ ] 中文标点符号(,。!?)
|
||
- [ ] 英文字符正确显示
|
||
- [ ] 大写字母(A-Z)
|
||
- [ ] 小写字母(a-z)
|
||
- [ ] 数字(0-9)
|
||
- [ ] 特殊符号正确显示
|
||
- [ ] 常用符号(@#$%)
|
||
- [ ] 数学符号(如果文章中有)
|
||
|
||
#### 2. 不同页面测试
|
||
- [ ] 首页文章列表字体正常
|
||
- [ ] 文章详情页字体正常
|
||
- [ ] 友链页面字体正常
|
||
- [ ] 归档页面字体正常
|
||
- [ ] 移动端字体正常
|
||
|
||
#### 3. 深色模式测试
|
||
- [ ] 深色模式下字体正常
|
||
- [ ] 切换主题时字体无闪烁
|
||
|
||
### 性能测试
|
||
|
||
#### 1. Network面板检查
|
||
- [ ] 字体文件大小显著减小
|
||
- [ ] 字体加载时间缩短
|
||
- [ ] 无404错误(字体文件路径正确)
|
||
|
||
#### 2. Lighthouse检查
|
||
- [ ] Performance得分提升
|
||
- [ ] FCP指标改善
|
||
- [ ] 无字体相关警告
|
||
|
||
### 兼容性测试
|
||
|
||
#### 浏览器测试
|
||
- [ ] Chrome(最新版)
|
||
- [ ] Firefox(最新版)
|
||
- [ ] Safari(最新版)
|
||
- [ ] Edge(最新版)
|
||
- [ ] 移动端Chrome
|
||
- [ ] 移动端Safari
|
||
|
||
---
|
||
|
||
## 🐛 故障排除
|
||
|
||
### 问题1:字符显示为方块(□)
|
||
|
||
**症状:** 某些中文字符显示为方块
|
||
|
||
**原因:** 子集字体未包含该字符
|
||
|
||
**解决方案:**
|
||
1. 检查生成的字符列表(used_chars.txt)
|
||
2. 确认该字符在HTML中存在
|
||
3. 重新运行子集化脚本
|
||
4. 或者手动添加该字符到字符列表
|
||
|
||
### 问题2:字体文件404错误
|
||
|
||
**症状:** 浏览器Console显示字体文件404
|
||
|
||
**原因:** CSS中的字体路径不正确
|
||
|
||
**解决方案:**
|
||
1. 检查字体文件是否在正确的目录
|
||
2. 确认CSS中的路径正确
|
||
3. 检查Hugo的静态文件配置
|
||
|
||
### 问题3:字体加载缓慢
|
||
|
||
**症状:** 字体加载时间没有明显改善
|
||
|
||
**原因:** 可能是网络问题或CDN配置
|
||
|
||
**解决方案:**
|
||
1. 检查字体文件是否真的减小了
|
||
2. 确认CDN缓存已更新
|
||
3. 使用浏览器禁用缓存重新测试
|
||
|
||
### 问题4:子集化后字符缺失
|
||
|
||
**症状:** 部分文章的特殊字符显示异常
|
||
|
||
**解决方案:**
|
||
```bash
|
||
# 方法1:扩大字符范围
|
||
# 修改glyphhanger命令,增加更多页面
|
||
glyphhanger https://your-blog.com/sitemap.xml \
|
||
--subset=themes/Ying/static/font/zql-v2.woff2 \
|
||
--formats=woff2,woff \
|
||
--output=themes/Ying/static/font/
|
||
|
||
# 方法2:使用更大的字符集(GB2312)
|
||
# 下载GB2312字符列表
|
||
curl -o gb2312_chars.txt https://example.com/gb2312_chars.txt
|
||
|
||
# 添加到字符集
|
||
cat gb2312_chars.txt >> used_chars.txt
|
||
sort -u used_chars.txt -o used_chars.txt
|
||
|
||
# 重新生成子集字体
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 回滚方案
|
||
|
||
如果优化后字体显示异常,快速回滚:
|
||
|
||
```bash
|
||
# 恢复原始字体文件
|
||
cp themes/Ying/static/font/zql-v2.woff2.bak themes/Ying/static/font/zql-v2.woff2
|
||
cp themes/Ying/static/font/zql-v2.woff.bak themes/Ying/static/font/zql-v2.woff
|
||
|
||
# 恢复CSS字体声明
|
||
# 将 zql-v2-subset.woff2 改回 zql-v2.woff2
|
||
```
|
||
|
||
或者恢复CSS中的字体声明:
|
||
|
||
```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;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 最佳实践
|
||
|
||
### 1. 定期更新子集字体
|
||
|
||
每当网站内容大幅更新时,重新运行子集化脚本:
|
||
|
||
```bash
|
||
# 每月或每季度运行一次
|
||
./themes/Ying/scripts/subset-fonts.sh online https://your-blog.com
|
||
```
|
||
|
||
### 2. 监控字体大小
|
||
|
||
在CI/CD流程中添加字体大小检查:
|
||
|
||
```yaml
|
||
# .github/workflows/build.yml
|
||
- name: Check font size
|
||
run: |
|
||
FONT_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2-subset.woff2)
|
||
if [ $FONT_SIZE -gt 500000 ]; then
|
||
echo "⚠️ 字体文件过大:$(numfmt --to=iec $FONT_SIZE)"
|
||
exit 1
|
||
fi
|
||
```
|
||
|
||
### 3. 使用CDN缓存
|
||
|
||
确保字体文件通过CDN分发,并设置长期缓存:
|
||
|
||
```nginx
|
||
# Nginx配置
|
||
location ~* \.(woff2|woff)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 完成确认清单
|
||
|
||
- [ ] 安装glyphhanger或fonttools
|
||
- [ ] 运行子集化脚本
|
||
- [ ] 生成子集字体文件
|
||
- [ ] 更新CSS字体声明
|
||
- [ ] 同步更新critical.css
|
||
- [ ] 测试字体显示
|
||
- [ ] 测试不同页面
|
||
- [ ] 测试深色模式
|
||
- [ ] 使用Lighthouse验证性能提升
|
||
- [ ] 提交代码
|
||
|
||
---
|
||
|
||
## 🎉 三步优化完成总结
|
||
|
||
### 第1步:CSS加载优化 ✅
|
||
- 关键CSS内联到<head>
|
||
- 非关键CSS异步加载
|
||
- 预期FCP提升20-30%
|
||
|
||
### 第2步:JS按需加载优化 ✅
|
||
- 核心JS始终加载
|
||
- 页面特定JS按需加载
|
||
- 非关键JS延迟加载
|
||
- 预期TTI提升25-35%
|
||
|
||
### 第3步:字体子集化优化 ⏳
|
||
- 自动提取实际使用的字符
|
||
- 生成子集字体
|
||
- 预期字体大小减少80-90%
|
||
|
||
---
|
||
|
||
## 📊 总体性能提升预估
|
||
|
||
| 指标 | 优化前 | 优化后 | 提升 |
|
||
|------|--------|--------|------|
|
||
| **CSS大小** | ~500KB | ~20KB (首屏) | ⚡ -96% |
|
||
| **JS大小** | ~800KB | ~350KB (首页) | ⚡ -56% |
|
||
| **字体大小** | ~1.5MB | ~200KB | ⚡ -87% |
|
||
| **FCP** | ~2.5s | ~1.2s | ⚡ -52% |
|
||
| **TTI** | ~4s | ~2s | ⚡ -50% |
|
||
| **Lighthouse得分** | ~60 | ~90+ | ⚡ +50% |
|
||
|
||
---
|
||
|
||
**文档版本:** v1.0
|
||
**创建日期:** 2026-06-03
|
||
**优化工具:** Claude AI
|