Files
blog/themes/Ying/OPTIMIZATION_STEP3_PLAN.md
T

609 lines
15 KiB
Markdown
Raw Normal View History

2026-06-03 12:29:56 +08:00
# 第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