Files
blog/themes/Ying/OPTIMIZATION_STEP1_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

16 KiB
Raw Blame History

第1步:CSS加载策略优化实施方案

📋 当前状态分析

CSS文件清单

  • normalize.css - CSS重置(基础,必须首屏加载)
  • remixicon.css - 图标字体(必须首屏加载)
  • uikit.min.css - UIkit框架(必须首屏加载)
  • nprogress.min.css - 进度条(核心功能,首屏加载)
  • message.min.css - 消息提示(可延迟)
  • main.css - 主题样式(需要拆分)

main.css 结构分析

首屏关键CSS(约15-20KB)

1. CSS变量定义(:root)
2. @font-face 字体声明
3. html/body 基础样式
4. .main 容器布局(居中、阴影、背景)
5. .header 头部布局
6. .header-menu 导航菜单
7. .header-avatar 头像
8. .header-social 社交链接
9. .header-bio 个人介绍
10. [data-theme="dark"] 深色模式基础变量
11. 基础排版(h1-h6, p, a, ul)
12. .footer 页脚
13. 滚动条样式
14. 基础图片样式
15. hr 分割线

非首屏CSS(可延迟加载,约35-40KB)

1. 代码相关
   - .code-fold-container
   - .code-expand-btn
   - pre, code 特殊样式
   - 代码高亮主题

2. 文章详情页
   - blockquote 样式
   - 文章列表特殊样式
   - 打赏按钮(.reward-container)
   - 相关文章(.related-posts)

3. 评论系统
   - Artalk 相关样式(.atk-*)
   - 评论区布局

4. 友链页面
   - .links-item
   - .link-active-badge
   - .link-dead-badge

5. 弹幕系统
   - .barrage-container
   - 弹幕动画

6. 搜索功能
   - .search-modal
   - .search-results

7. 其他功能
   - 浮动工具(.floating-tools)
   - Toast 提示
   - 图片灯箱
   - 无限滚动加载提示

🎯 优化方案

策略1:CSS代码拆分(推荐)

将 main.css 拆分为两个文件:

  • critical.css - 首屏关键CSS(内联到HTML)
  • main.css - 其余样式(异步加载)

策略2:CSS层级加载

保留单个 main.css,但使用Hugo的模板逻辑分层输出:

  • 首屏样式:内联 <style> 标签
  • 其余样式:<link rel="preload"> 异步加载

📝 实施步骤

步骤1.1:创建关键CSS文件

文件路径: themes/Ying/assets/css/critical.css

内容: 从main.css提取首屏关键样式

/* ========================================
   首屏关键CSS - Critical CSS
   用于内联到<head>中,提升FCP
   ======================================== */

/* CSS变量 */
:root {
    --background-color: #fafafa;
    --background-dark-color: #020617;
    --text-content-color: #24292e;
    --text-content-dark-color: #d1d5db;
    --text-title-color: #111827;
    --text-title-dark-color: #e5e7eb;
    --text-highlight-color: #696565;
    --frame-dark-color: #1f2933;
    --frame-color: #eee;
    --card-shadow-light: 0 10px 20px 0 rgba(236, 236, 236, 0.86);
    --card-shadow-dark: 0 18px 45px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(148, 163, 184, 0.45);
    --link-color-light: #374151;
    --link-hover-color-light: #111827;
    --link-color-dark: #e5e7eb;
    --link-hover-color-dark: #d1d5db;
}

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

/* 基础样式 */
html {
    scroll-behavior: smooth;
}

body {
    font-family: 'zql', serif;
    color: var(--text-content-color);
    line-height: 1.6;
    font-size: 16px;
    background: var(--background-color);
    /* 背景图片 */
    background-image: url(data:image/webp;base64,...); /* 实际base64数据
    background-repeat: round;
    background-size: contain;
    background-attachment: fixed;
    z-index: 5;
    transition: background-color 0.3s ease, color 0.3s ease;
}

/* 深色模式基础 */
[data-theme="dark"] body {
    color: var(--text-content-dark-color);
    background-color: var(--background-dark-color);
}

[data-theme="dark"] .main {
    background: rgba(15, 23, 42, 0.99);
    box-shadow: var(--card-shadow-dark);
}

/* 主容器 */
.main {
    max-width: 618px;
    overflow: hidden;
    margin: 60px auto;
    padding: 50px;
    background: rgba(255, 255, 255, 0.8);
    box-shadow: var(--card-shadow-light);
    border-radius: 8px;
    z-index: 10;
    position: relative;
    text-align: center;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: background-color 0.5s cubic-bezier(0.4, 0, 0.2, 1),
                color 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}

/* 头部布局 */
.header {
    margin-bottom: 10px;
    padding-bottom: 10px;
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

.header-top {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
}

.header-avatar {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    overflow: hidden;
    cursor: pointer;
    position: relative;
}

.header-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
    box-shadow: none;
}

.header-info {
    text-align: right;
}

.header-social {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-bottom: 8px;
}

.header-social a {
    color: var(--link-color-light);
    font-size: 1.2rem;
    transition: color 0.3s;
}

.header-social a:hover {
    color: var(--link-hover-color-light);
}

.header-bio {
    font-size: 0.9rem;
    color: #666;
}

/* 导航菜单 */
.header-menu {
    border-top: 1px solid #eee;
    padding-top: 10px;
}

.header-menu ul {
    display: flex;
    justify-content: center;
    list-style: none;
    gap: 25px;
    padding: 0;
    margin: 0;
}

.header-menu ul li a {
    color: var(--link-color-light);
    text-decoration: none;
    font-size: 0.95rem;
    transition: color 0.3s;
}

.header-menu ul li.active a {
    color: var(--text-title-color);
    font-weight: 600;
}

/* 深色模式导航 */
[data-theme="dark"] .header nav {
    border-top-color: #1f2933;
}

[data-theme="dark"] .header nav ul.flat li a {
    color: #9ca3af;
}

[data-theme="dark"] .header nav ul.flat li.active a {
    color: #e5e7eb;
}

[data-theme="dark"] .header-social a {
    color: var(--link-color-dark);
}

[data-theme="dark"] .header-social a:hover {
    color: var(--link-hover-color-dark);
}

[data-theme="dark"] .header-bio {
    color: #9ca3af;
}

/* 排版 */
h1, h2, h3, h4, h5, h6 {
    line-height: 1.2;
    font-weight: 400;
}

h1 { font-size: 1.5rem; }
h2 { font-size: 1.375rem; }
h3 { font-size: 1.25rem; }
h4, h5, h6 { font-size: 1.125rem; }

a {
    color: var(--link-color-light);
    text-decoration: none;
    transition: color 0.3s;
}

a:hover {
    color: var(--link-hover-color-light);
}

[data-theme="dark"] a {
    color: var(--link-color-dark);
}

[data-theme="dark"] a:hover {
    color: var(--link-hover-color-dark);
}

p {
    margin: 0.8em 0;
}

ul {
    padding-left: 0px;
    list-style-type: none;
}

/* 图片基础样式 */
img {
    max-width: 100%;
    border-radius: 5px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
    vertical-align: middle;
}

a img {
    border: none;
}

/* 分割线 */
hr {
    display: block;
    height: 1px;
    border: 0;
    border-top: 1px solid #cccccc;
    margin: 1rem 0;
    padding: 0;
}

[data-theme="dark"] hr {
    border-top-color: rgba(255, 255, 255, 0.2);
}

/* 滚动条 */
::-webkit-scrollbar {
    width: 13px;
}

::-webkit-scrollbar-button {
    display: none;
}

::-webkit-scrollbar-thumb {
    min-height: 16px;
    background-color: #999999;
    background-clip: padding-box;
    border: 3px solid #fdfdfd;
    border-radius: 5px;
}

::selection {
    background: #444;
    color: #fff;
}

/* 图标 */
.icon {
    width: 1.8em;
    height: 1.8em;
    vertical-align: -1em;
    fill: currentColor;
    overflow: hidden;
}

/* 文章元信息 */
.meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    gap: 10px;
    margin-bottom: 20px;
    color: #999;
    font-size: 0.9rem;
    line-height: 1.6;
}

.meta-item {
    white-space: nowrap;
}

[data-theme="dark"] .meta {
    color: #9ca3af;
}

/* 页脚 */
.footer {
    border-top: 1px solid #eee;
    padding-top: 20px;
    margin-top: 30px;
    color: #999;
    font-size: 0.85rem;
}

[data-theme="dark"] .footer {
    border-top-color: #1f2933;
    color: #6b7280;
}

/* 文章标题 */
.post-title {
    color: var(--text-title-color);
    margin-bottom: 0.5em;
}

[data-theme="dark"] .post-title {
    color: var(--text-title-dark-color);
}

/* 深色模式链接和文本 */
[data-theme="dark"] .post-content {
    color: var(--text-content-dark-color);
}

/* 主题切换按钮 */
.theme-toggle-button {
    border: none;
    background: transparent;
    cursor: pointer;
    font-size: 14px;
    padding: 4px 8px;
    color: #586069;
}

.theme-toggle-button:focus {
    outline: none;
}

[data-theme="dark"] .theme-toggle-button {
    color: #e5e7eb;
}

/* 响应式 - 大屏优化 */
@media (min-width: 768px) {
    .main {
        background: rgba(255, 255, 255, 0.88);
        -webkit-backdrop-filter: blur(6px);
        backdrop-filter: blur(6px);
    }
}

/* 主题切换动画 */
#theme-toggle i {
    transition: transform 0.5s cubic-bezier(0.4, 0.0, 0.2, 1);
}

#theme-toggle.rotate i {
    transform: rotate(360deg);
}

步骤1.2:修改 head.html - 实施CSS加载策略

文件路径: themes/Ying/layouts/partials/head.html

修改位置: 第127-135行(Stylesheets部分)

当前代码:

{{ $normalize := resources.Get "css/libs/normalize.css" }}
{{ $remixicon := resources.Get "css/libs/remixicon.css" }}
{{ $uikit := resources.Get "css/libs/uikit.min.css" }}
{{ $nprogress := resources.Get "css/libs/nprogress.min.css" }}
{{ $message := resources.Get "css/libs/message.min.css" }}
{{ $main := resources.Get "css/main.css" }}

{{ $style := slice $normalize $remixicon $uikit $nprogress $message $main | resources.Concat "css/style.css" | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $style.RelPermalink }}">

修改后代码:

{{/* ====== CSS加载优化:关键CSS内联 + 非关键CSS异步加载 ====== */}}

{{/* 1. 首屏关键CSS - 内联到<head> */}}
{{ $critical := resources.Get "css/critical.css" | resources.Minify }}
<style>{{ $critical.Content | safeCSS }}</style>

{{/* 2. 非首屏CSS - 预加载但不阻塞渲染 */}}
{{ $normalize := resources.Get "css/libs/normalize.css" }}
{{ $remixicon := resources.Get "css/libs/remixicon.css" }}
{{ $uikit := resources.Get "css/libs/uikit.min.css" }}
{{ $nprogress := resources.Get "css/libs/nprogress.min.css" }}
{{ $message := resources.Get "css/libs/message.min.css" }}
{{ $main := resources.Get "css/main.css" }}

{{/* 合并非关键CSS */}}
{{ $nonCritical := slice $normalize $remixicon $uikit $nprogress $message $main | resources.Concat "css/style.css" | resources.Minify | resources.Fingerprint }}

{{/* 使用preload异步加载,不阻塞渲染 */}}
<link rel="preload" href="{{ $nonCritical.RelPermalink }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ $nonCritical.RelPermalink }}"></noscript>

步骤1.3:创建Hugo构建脚本(可选,用于自动化)

文件路径: themes/Ying/scripts/extract-critical-css.sh

#!/bin/bash
# 从main.css提取关键CSS的辅助脚本
# 用法:./scripts/extract-critical-css.sh

echo "正在分析main.css..."

# 标记:以下选择器为首屏关键CSS
CRITICAL_SELECTORS=(
    ":root"
    "html"
    "body"
    ".main"
    ".header"
    ".header-top"
    ".header-avatar"
    ".header-info"
    ".header-social"
    ".header-bio"
    ".header-menu"
    "h1"
    "h2"
    "h3"
    "h4"
    "h5"
    "h6"
    "a"
    "p"
    "ul"
    "img"
    "hr"
    ".meta"
    ".meta-item"
    ".footer"
    ".post-title"
    ".theme-toggle-button"
    "@media"
    "@font-face"
    ":root"
    "[data-theme"
)

echo "请根据以上选择器手动提取CSS到critical.css"
echo "或使用自动化CSS提取工具如:critical、penthouse"

🧪 测试验证清单

测试1:首屏渲染性能

  • 使用Chrome DevTools的Network面板查看:
    • 首字节时间(TTFB)是否正常
    • 首次内容绘制(FCP)是否提升
    • 样式表是否正确加载

测试2:视觉一致性

  • 首页显示正常
    • Header布局正确
    • 导航菜单样式正确
    • 头像显示正常
    • 社交链接图标正确
    • 深色模式切换正常
  • 文章列表页显示正常
    • 文章卡片样式正确
    • 分页按钮正常

测试3:深色模式

  • 点击头像切换深色模式
  • 所有文字颜色正确
  • 背景颜色正确
  • 阴影效果正确
  • 链接颜色正确

测试4:响应式布局

  • 桌面端(>768px)显示正常
  • 移动端(<768px)显示正常
  • 菜单在移动端正确折叠

测试5:资源加载

  • 检查Console无错误
  • 所有CSS文件加载成功
  • 字体文件加载成功(Network面板)
  • 无404错误

📊 预期收益

性能指标提升(预估)

指标 优化前 优化后 提升
首屏CSS大小 ~500KB ~20KB(内联) ⚡ -96%
CSS阻塞时间 ~800ms ~0ms ⚡ -100%
FCP ~1.8s ~1.2s ⚡ -33%
Speed Index ~2.5s ~1.8s ⚡ -28%

用户体验提升

  • ✅ 页面立即开始渲染(无需等待CSS下载)
  • ✅ 深色模式无闪烁(CSS变量立即生效)
  • ✅ 字体加载更平滑(font-display: swap生效)
  • ✅ 首屏内容更快可见

⚠️ 注意事项

1. 背景图片的base64数据

当前body的background-image使用了很长的base64数据。这个数据应该:

  • 保持在critical.css中(确保首屏背景显示)
  • 或者移至单独文件并使用preload

建议:将base64数据移至单独的CSS文件 background.css,使用 fetchpriority="high" 优先加载

2. Remixicon字体文件

remixicon.css 引用了字体文件(.woff2, .woff)。确保:

  • 字体文件路径正确
  • 使用 font-display: swap 避免FOIT

3. UIkit依赖

UIkit的CSS可能包含一些组件样式(如grid、modal等)。检查:

  • 深色模式是否正确覆盖UIkit默认样式
  • 是否有样式冲突

4. 浏览器兼容性

<link rel="preload" onload> 技术需要测试:

  • Chrome/Edge:✅ 完全支持
  • Firefox:✅ 支持
  • Safari:✅ 支持(11.1+)
  • IE11:❌ 不支持(需要polyfill或fallback)

🔄 回滚方案

如果出现问题,快速回滚到原始方案:

{{/* 回滚到原始CSS加载方式 */}}
{{ $normalize := resources.Get "css/libs/normalize.css" }}
{{ $remixicon := resources.Get "css/libs/remixicon.css" }}
{{ $uikit := resources.Get "css/libs/uikit.min.css" }}
{{ $nprogress := resources.Get "css/libs/nprogress.min.css" }}
{{ $message := resources.Get "css/libs/message.min.css" }}
{{ $main := resources.Get "css/main.css" }}

{{ $style := slice $normalize $remixicon $uikit $nprogress $message $main | resources.Concat "css/style.css" | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $style.RelPermalink }}">

📚 扩展阅读

  1. Critical CSS最佳实践

  2. CSS加载策略

  3. 性能优化工具


✅ 实施清单

  • 创建 themes/Ying/assets/css/critical.css
  • 修改 themes/Ying/layouts/partials/head.html
  • 测试首页显示
  • 测试深色模式
  • 测试移动端
  • 使用Lighthouse验证性能提升
  • 提交代码

预计耗时: 1-2小时 风险等级: 低(有完整回滚方案)