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

15 KiB
Raw Blame History

第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

# 使用npm全局安装
npm install -g glyphhanger

# 或者使用pip安装fonttools
pip install fonttools brotli

# 或者使用subfont
npm install -g subfont

步骤3.2:扫描网站提取字符

方法1:从本地构建文件扫描

# 先构建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:从在线网站扫描(推荐)

# 扫描在线网站(需要先部署或本地预览)
glyphhanger https://your-blog-url.com \
  --subset=themes/Ying/static/font/zql-v2.woff2 \
  --formats=woff2 \
  --output=themes/Ying/static/font/

方法3:使用fonttools手动提取

# 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行):

@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;
    /* 不再需要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行):

/* 字体声明 */
@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;
}

🔧 自动化脚本

创建构建脚本

文件路径: themes/Ying/scripts/subset-fonts.sh

#!/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

设置执行权限

chmod +x themes/Ying/scripts/subset-fonts.sh

使用方法

# 方法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:子集化后字符缺失

症状: 部分文章的特殊字符显示异常

解决方案:

# 方法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

# 重新生成子集字体

🔄 回滚方案

如果优化后字体显示异常,快速回滚:

# 恢复原始字体文件
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中的字体声明:

/* 回滚到原始字体 */
@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. 定期更新子集字体

每当网站内容大幅更新时,重新运行子集化脚本:

# 每月或每季度运行一次
./themes/Ying/scripts/subset-fonts.sh online https://your-blog.com

2. 监控字体大小

在CI/CD流程中添加字体大小检查:

# .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配置
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