16 KiB
16 KiB
第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 }}">
📚 扩展阅读
-
Critical CSS最佳实践
-
CSS加载策略
-
性能优化工具
- Lighthouse:https://developers.google.com/web/tools/lighthouse
- WebPageTest:https://www.webpagetest.org/
✅ 实施清单
- 创建
themes/Ying/assets/css/critical.css - 修改
themes/Ying/layouts/partials/head.html - 测试首页显示
- 测试深色模式
- 测试移动端
- 使用Lighthouse验证性能提升
- 提交代码
预计耗时: 1-2小时 风险等级: 低(有完整回滚方案)