Files
blog/themes/Ying/OPTIMIZATION_STEP3_PLAN.md
T
Vaica cbf2034f78 perf: JS按需加载优化 + PJAX适配
- 首页JS减少56%(800KB → 350KB)
- 文章页JS减少44%(800KB → 450KB)
- 完美适配PJAX导航
- 保持所有功能正常
2026-06-03 12:29:56 +08:00

610 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第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