✨我是辰海星的「文章捕手」,擅长在文字的星海中打捞精华。
这篇文章主要是在讲:怎样把 Hexo 博客适配到 shokax-can / shokaX 这一套主题环境中,并进一步完成版本回退、依赖调整、主题配置、功能增强、页面美化、留言板与说说页面接入,以及最后用 GitHub Actions 自动部署。整体上,它更像是一份“博客搭建与个性化改造记录”,内容偏实操,适合照着一步步配置。 🛠️ 1. 拉取主题代码 📦 先通过 git clone 拉取 shokax-can 仓库,并且使用浅克隆方式,减少下载内容,加快获取速度。 2. 回退到 0.5.1 版本 🔁 文章特别说明了需要把 hexo-theme-shokax 回退到 0.5.1。 同时还要: 1)删除不需要的包 hexo-renderer-aether 2)把主题版本改成 0.5.1 3)补充安装 hexo-blog-encrypt、hexo-deployer-git、hexo-renderer-multi-next-markdown-it 等依赖 这一步的重点是保证主题兼容性和后续功能正常运行。 3. 配置 _config.shokax.yml 和 _config.yml ⚙️ 文章建议下载主题默认配置文件后再做个性化修改,比如设置图片源 image_server。 另外,在站点的 _config.yml 里加入 markdown 渲染设置,包括: 1)是否允许 HTML 2)换行、链接自动识别等规则 3)目录与锚点插件 4)多行表格插件 5)注音插件 6)剧透插件 这一部分的作用是让文章渲染效果更符合 shoka 系主题需求。 4. 增加适配脚本 💻 这是文章里很重要的一部分,主要加了几个 scripts 脚本: 1)hotReload.js 实现 Hexo 本地开发时的热更新。 它会轮询页面内容变化,发现更新后自动刷新,并且还能恢复页面滚动位置。 还提到支持加密页面。 2)patchSiteInit.js 自动修改主题里的 siteInit.ts 文件,注入 hexo-blog-decrypt 事件监听。 主要用来修复密码保护文章在解密后渲染异常的问题。 3)sakura.js 往主题头部注入樱花特效模板文件,让页面能显示飘落樱花效果。 4)summaryTyping.js 给文章标题和摘要添加打字机动画,同时还能显示 AI 模型徽章。 比如会读取配置中的模型名,默认是 gpt-5-nano。 5. 自定义 source/data 资源目录 🎨 文章展示了 source/data 下的目录结构,包括头像、颜色样式、布局样式、自定义样式、多语言文件等。 这里的核心是把主题进一步“改成自己的样子”。 6. 自定义样式优化页面外观 ✨ 文中写了很多 stylus 样式,主要包括: 1)修改首页文章标题颜色 2)修改选中文字时的背景色 3)让个人描述文字显示渐变动画 4)调整 AI 总结区域文字颜色 5)修改首页文章摘要颜色 6)修复页面滑动条卡顿问题 7)让 AI 总结支持换行显示 8)让导航栏更透明 9)调整头像边框样式 10)给站点标题增加循环打字机动画 11)适配移动端,避免小屏显示异常 12)修改首页轮播图、页头、工具栏等高度 这部分说明作者不仅关注功能,也很重视视觉效果。 7. 修改语言提示内容 🌟 通过 languages.yml,可以自定义网页标签页失焦和重新聚焦时显示的文字。 例如: 显示时提示“你回来啦” 隐藏时提示“别走” 这种小细节能提升博客的趣味性。 8. 接入樱花特效页面组件 🌸 在 views 下创建 sakura.pug,并引入外部樱花脚本。 还能配置樱花数量、速度、方向、层级等参数。 作用就是让页面背景更有动态感。 9. 调整文章默认模板 📝 修改 scaffolds/post.md,让新建文章时自动带上: 1)标题 2)日期 3)分类 4)置顶字段 5)描述 6)sitemap 字段 这样每次写新文章时就不用重复手动补这些内容。 10. 配置留言板 💬 文章提到留言板功能可参考 Twikoo 来实现。 也就是说,博客可以加入评论或留言互动模块。 11. 配置说说页面 📱 文章还介绍了“说说”页面的接入方式,参考的是 daodao 项目。 主要做法是: 1)在页面中引入对应 JS 脚本 2)添加容器 div 3)配置头像、名称、数量限制、接口地址、标题等信息 这样就能拥有类似动态、碎碎念的独立页面。 12. 使用 GitHub Actions 自动部署 🚀 最后一部分是自动化部署配置。 主要流程包括: 1)监听 master 分支的指定文件变化 2)拉取仓库和子模块 3)安装 Node.js 20 4)安装 pnpm 和 hexo-cli 5)安装项目依赖 6)执行 hexo clean、hexo generate、hexo algolia 7)配置 Git 用户信息 8)将生成的静态文件自动部署到 GitHub Pages 仓库 这一部分的意义是:以后只要推送代码,就能自动发布博客。 重点来说,这篇文章最核心的内容有三类: 1)把 shokaX 主题稳定跑起来,包括版本和依赖修正 2)通过脚本补齐热更新、加密页面兼容、摘要打字机等增强功能 3)通过样式、自定义页面和自动部署,把博客从“能用”改造成“好看又好维护”的状态 ✨ 整体看下来,这是一篇偏完整的 Hexo 博客个性化改造记录,既包含基础安装,也包含进阶美化和自动化部署。
# 拉取 shokax-can
git clone https://github.com/theme-shoka-x/shokax-can --depth=1 |
# 回退 0.5.1
删除 package.json 中的包
"hexo-renderer-aether": "^0.0.6", |
修改包
"hexo-theme-shokax": "0.5.1", |
添加包
"hexo-blog-encrypt": "^3.1.9", | |
"hexo-deployer-git": "^4.0.0", | |
"hexo-renderer-multi-next-markdown-it": "^0.2.1", |
# 配置_config.shokax.yml 和 _config.yml
下载 _config.shokax.yml ,个性化配置
image_server: "https://t.alcy.cc/moez" |
在_config.yml 中添加
markdown: | |
render: # 渲染器设置 | |
html: false # 过滤 HTML 标签 | |
xhtmlOut: true # 使用 '/' 来闭合单标签 (比如 <br />)。 | |
breaks: true # 转换段落里的 '\n' 到 <br>。 | |
linkify: true # 将类似 URL 的文本自动转换为链接。 | |
typographer: | |
quotes: "“”‘’" | |
plugins: # markdown-it 插件设置 | |
- plugin: | |
name: markdown-it-toc-and-anchor | |
enable: true | |
options: # 文章目录以及锚点应用的 class 名称,shoka 系主题必须设置成这样 | |
tocClassName: "toc" | |
anchorClassName: "anchor" | |
- plugin: | |
name: markdown-it-multimd-table | |
enable: true | |
options: | |
multiline: true | |
rowspan: true | |
headerless: true | |
- plugin: | |
name: ./markdown-it-furigana | |
enable: true | |
options: | |
fallbackParens: "()" | |
- plugin: | |
name: ./markdown-it-spoiler | |
enable: true | |
options: | |
title: "你知道得太多了" |
# 适配脚本
scripts/ | |
├─ hotReload.js | |
├─ patchSiteInit.js | |
├─ sakura.js | |
└─ summaryTyping.js |
/** | |
* Hexo 开发模式热更新脚本 | |
* 原理:轮询检测服务器返回的内容变化,刷新页面并恢复滚动位置 | |
* 仅在 hexo server 模式下生效 | |
* 支持密码保护页面(hexo-blog-encrypt) | |
*/ | |
hexo.extend.filter.register('after_render:html', function(str) { | |
// 仅开发模式启用 | |
if (process.env.NODE_ENV === 'production') return str; | |
const script = ` | |
<script> | |
(function() { | |
if (location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') return; | |
const POLL_INTERVAL = 1000; | |
const STORAGE_KEY = '_hotReloadScroll'; | |
const contentSelector = '.body.md'; | |
let lastContent = null; // null 表示尚未初始化 | |
// 恢复滚动位置 | |
const saved = sessionStorage.getItem(STORAGE_KEY); | |
if (saved) { | |
sessionStorage.removeItem(STORAGE_KEY); | |
requestAnimationFrame(() => window.scrollTo(0, parseInt(saved, 10))); | |
} | |
// 从 HTML 字符串中提取内容用于比较 | |
function extractContent(html) { | |
const parser = new DOMParser(); | |
const doc = parser.parseFromString(html, 'text/html'); | |
const el = doc.querySelector(contentSelector); | |
if (!el) return ''; | |
// 使用 textContent 忽略 HTML 结构差异 | |
return el.textContent.replace(/\\s+/g, ' ').trim(); | |
} | |
async function checkUpdate() { | |
try { | |
const res = await fetch(location.href, { cache: 'no-store' }); | |
const html = await res.text(); | |
const newContent = extractContent(html); | |
// 首次运行,记录初始内容 | |
if (lastContent === null) { | |
lastContent = newContent; | |
console.log('[HotReload] 已初始化'); | |
return; | |
} | |
if (newContent !== lastContent) { | |
console.log('[HotReload] 检测到内容变化,刷新页面'); | |
sessionStorage.setItem(STORAGE_KEY, window.scrollY.toString()); | |
location.reload(); | |
return; | |
} | |
} catch (e) { | |
console.error('[HotReload] 检查更新失败:', e); | |
} | |
} | |
// 立即执行一次获取初始内容,然后开始轮询 | |
checkUpdate(); | |
setInterval(checkUpdate, POLL_INTERVAL); | |
console.log('[HotReload] 已启用'); | |
})(); | |
</script> | |
`; | |
return str.replace('</body>', script + '</body>'); | |
}); |
/** | |
* 自动为 hexo-theme-shokax 的 siteInit.ts 注入 hexo-blog-decrypt 事件监听 | |
* 解决密码保护页面解密后渲染错误的问题 | |
*/ | |
const fs = require('fs'); | |
const path = require('path'); | |
hexo.on('generateBefore', function() { | |
const siteInitPath = path.join( | |
hexo.base_dir, | |
'node_modules/hexo-theme-shokax/source/js/_app/pjax/siteInit.ts' | |
); | |
// 检查文件是否存在 | |
if (!fs.existsSync(siteInitPath)) { | |
hexo.log.warn('[PatchSiteInit] 未找到 siteInit.ts,跳过补丁'); | |
return; | |
} | |
let content = fs.readFileSync(siteInitPath, 'utf8'); | |
// 检查是否已经包含补丁代码 | |
if (content.includes("window.addEventListener('hexo-blog-decrypt'")) { | |
hexo.log.debug('[PatchSiteInit] 补丁已存在,跳过'); | |
return; | |
} | |
// 查找插入位置:在 resize 事件监听之后 | |
const resizeListenerPattern = /window\.addEventListener\('resize',\s*resizeHandle,\s*\{[\s\S]*?passive:\s*true[\s\S]*?\}\)/; | |
if (!resizeListenerPattern.test(content)) { | |
hexo.log.warn('[PatchSiteInit] 未找到 resize 事件监听器,无法注入补丁'); | |
return; | |
} | |
// 注入代码 | |
const patchCode = ` | |
window.addEventListener('hexo-blog-decrypt', siteRefresh, { | |
passive: true | |
})`; | |
content = content.replace(resizeListenerPattern, (match) => { | |
return match + patchCode; | |
}); | |
// 写回文件 | |
fs.writeFileSync(siteInitPath, content, 'utf8'); | |
hexo.log.info('[PatchSiteInit] 已成功注入 hexo-blog-decrypt 事件监听'); | |
}); |
hexo.extend.filter.register('theme_inject', function(injects) { | |
injects.head.file('sakura', 'views/sakura.pug', {}, {cache: true}); | |
}); |
/** | |
* Hexo 过滤器:为文章标题和摘要添加打字机效果,并显示 AI 模型标识 | |
*/ | |
hexo.extend.filter.register('after_render:html', function(str) { | |
// 获取 AI 模型名称配置 | |
const modelName = hexo.theme.config?.summary?.model | |
|| hexo.config?.summary?.model | |
|| 'gpt-5-nano'; | |
// 客户端脚本:实现打字机效果和模型标识 | |
const script = ` | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// 1. 为文章标题添加打字机效果 | |
const headline = document.querySelector('h1[itemprop="name headline"]'); | |
if (headline && !headline.dataset.typed) { | |
const text = headline.textContent; | |
headline.textContent = ''; | |
headline.dataset.typed = '1'; | |
let index = 0; | |
(function typeHeadline() { | |
if (index < text.length) { | |
headline.textContent += text[index++]; | |
setTimeout(typeHeadline, 50); | |
} | |
})(); | |
} | |
// 2. 为摘要标签页的段落添加打字机效果 | |
document.querySelectorAll('.tab[data-id="summary"] p').forEach(paragraph => { | |
if (paragraph.dataset.typed) return; | |
const text = paragraph.textContent; | |
paragraph.textContent = ''; | |
paragraph.dataset.typed = '1'; | |
let index = 0; | |
(function typeParagraph() { | |
if (index < text.length) { | |
paragraph.textContent += text[index++]; | |
setTimeout(typeParagraph, 30); | |
} | |
})(); | |
}); | |
// 3. 添加 AI 模型标识徽章 | |
const specialList = document.querySelector('ul.special'); | |
if (specialList && !specialList.parentElement.querySelector('.model-badge')) { | |
const badge = document.createElement('div'); | |
badge.className = 'model-badge'; | |
badge.textContent = '${modelName}'; | |
badge.style.cssText = | |
'position:absolute;' + | |
'top:8px;' + | |
'right:8px;' + | |
'font-size:11px;' + | |
'color:var(--text-color,#666);' + | |
'opacity:0.6;' + | |
'padding:2px 6px;' + | |
'border:1px solid currentColor;' + | |
'border-radius:3px;' + | |
'z-index:10'; | |
specialList.parentElement.style.position = 'relative'; | |
specialList.parentElement.appendChild(badge); | |
} | |
}); | |
</script> | |
`; | |
// 在 </body> 标签前插入脚本 | |
return str.replace('</body>', script + '</body>'); | |
}); |
source/data/ | |
├─ assets/ | |
│ └─ avatar.jpeg | |
├─ css/ | |
│ ├─ colors.styl | |
│ └─ layout.styl | |
├─ custom.styl | |
└─ languages.yml |

// 首页文章标题颜色 | |
[data-theme="dark"]:root { | |
--color-red: #0fd8fee6 | |
--color-pink: #38a6e5cc | |
--color-blue: #f3cdce | |
} | |
// 选中文字的背景颜色 | |
::selection { | |
background: #0fd8fee6 !important; | |
} | |
// 个人描述颜色 - 5 色渐变 | |
.description{ | |
background: linear-gradient(135deg, #0fd8fe, #38a6e5, #8b5cf6, #f472b6, #fb923c) !important; | |
background-clip: text !important; | |
-webkit-background-clip: text !important; | |
-webkit-text-fill-color: transparent !important; | |
color: transparent !important; | |
background-size: 200% 200% !important; | |
animation: gradient-shift 3s ease-in-out infinite !important; | |
} | |
// 渐变色动画效果 | |
@keyframes gradient-shift { | |
0%, 100% { | |
background-position: 0% 50%; | |
} | |
50% { | |
background-position: 100% 50%; | |
} | |
} | |
// 文章中的 AI 总结字体颜色 | |
.tab[data-id="summary"] p { | |
color #1ebde6 | |
} | |
// 首页的文章内容颜色 | |
.excerpt { | |
color #ca88b6 | |
} |
// 修复卡滑动条的 bug | |
main { | |
>.inner { | |
min-height: 90vh; | |
} | |
} | |
//AI 总结能换行 | |
.tab[data-id="summary"] p { | |
white-space: pre-line; | |
} | |
// 导航栏透明 | |
#nav { | |
backdrop-filter: saturate(100%) blur(0px) !important; | |
} | |
// 头像框样式 | |
.overview .author .image { | |
border-radius: 10% !important; | |
box-shadow: 0 0 0rem .0rem !important; | |
border: 0 !important; | |
max-width: 100% !important; | |
} | |
// 循环打字机动画效果 | |
@keyframes typewriter-cycle { | |
0% { | |
width: 0; | |
} | |
25% { | |
width: 100%; | |
} | |
75% { | |
width: 100%; | |
} | |
100% { | |
width: 0; | |
} | |
} | |
@keyframes blink-cursor { | |
from, to { | |
border-color: transparent; | |
} | |
50% { | |
border-color: var(--text-color, #333); | |
} | |
} | |
// 只作用于 .logo 内的 title 打字机效果 | |
.logo .title { | |
overflow: hidden; | |
white-space: nowrap; | |
border-right: 2px solid var(--text-color, #333); | |
animation: typewriter-cycle 6s ease-in-out infinite, blink-cursor 0.75s step-end infinite; | |
animation-delay: 0.8s, 0.8s; | |
width: 0; | |
} | |
// 响应式调整 | |
@media (max-width: 768px) { | |
.logo .artboard, .logo .title { | |
white-space: normal; | |
overflow: visible; | |
border-right: none; | |
animation: none; | |
width: auto; | |
} | |
} | |
// 修改首页轮播图大小 | |
#brand { | |
height: 75vh !important; | |
} | |
#header { | |
height: 85vh !important; | |
} | |
#imgs { | |
height: 100vh !important; | |
} | |
#tool { | |
top: 90vh !important; | |
} | |
.waves { | |
margin-bottom: -.6875rem important; | |
position: absolute important; | |
} |
@import "css/colors.styl" | |
@import "css/layout.styl" |
# language | |
zh-CN: | |
# items | |
favicon: | |
show: 🌟你回来啦~喵😘 | |
hide: 🧊别走~喵😭 |
views/ | |
└─ sakura.pug |
script. | |
window.sakuraConfig = { | |
sakura: 30, | |
xSpeed: 0.5, | |
ySpeed: 0.5, | |
rSpeed: 0.03, | |
direction: "TopRight", | |
zIndex: -1 | |
}; | |
script(src="https://cdn.jsdelivr.net/gh/minz71/sakura-rain/sakura-rain.js" defer) |
# 调整默认模板
scaffolds/ | |
└─ post.md |
--- | |
title: <!--swig0--> | |
date: <!--swig1--> | |
categories: | |
- | |
sticky: false | |
description: | |
sitemap: | |
--- |
# 配置留言板
参考链接:twikoo
# 配置说说页面
参考链接:daodao
<head> | |
<script src="//cdn.jsdelivr.net/gh/Uyoahz26/daodao@main/dist/qexo-dao.min.js"></script> | |
</head> | |
<body> | |
<!-- ... --> | |
<div id="qexoDaoDao"></div> | |
<script> | |
qexoDaodao?.init({ | |
el: "#qexoDaoDao", | |
avatar: "【头像url】", | |
name: "【名称】", | |
limit: 5, | |
useLoadingImg: false, | |
baseURL: "【URL地址】", | |
title: "【主题名称】" | |
}).then(function (){ | |
console.log("qexoDaodao加载完成"); | |
}) | |
</script> | |
</body> |
# Git Actions 同步
.github/ | |
└─ workflows/ | |
└─ main.yml |
name: Deploy Hexo to GitHub Pages | |
on: | |
push: | |
branches: | |
- master | |
paths: | |
- '*.json' | |
- '**.yml' | |
- '**/source/**' | |
- '!**/source/_drafts/**' # 排除目录内文件 | |
- '!**/source/_drafts/' # 新增:排除目录本身 | |
- '**/themes/**' | |
jobs: | |
build: | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
submodules: recursive | |
fetch-depth: 0 | |
- uses: actions/setup-node@v4 | |
with: | |
node-version: 20 | |
- name: Configure PNPM | |
env: | |
SHELL: /usr/bin/bash # 设置 SHELL 环境变量 | |
run: | | |
npm install -g pnpm@latest | |
pnpm setup | |
PNPM_PATH=$(grep 'export PNPM_HOME=' ~/.bashrc | cut -d'"' -f2) | |
echo "$PNPM_PATH" >> $GITHUB_PATH | |
echo "PNPM_HOME=$PNPM_PATH" >> $GITHUB_ENV | |
- name: Install Hexo CLI | |
run: pnpm add -g hexo-cli | |
- name: Install Dependencies | |
run: pnpm install | |
- name: Generate static files | |
run: | | |
hexo clean | |
hexo generate | |
hexo algolia | |
- name: Deploy with hexo-deployer-git | |
env: | |
GIT_USER: $<!--swig2--> | |
GIT_EMAIL: $ | |
GH_TOKEN: $ | |
DEPLOY_REPO: $ | |
DEPLOY_BRANCH: main | |
run: | | |
git config --global user.email "$GIT_EMAIL" | |
git config --global user.name "$GIT_USER" | |
REPO_WITH_TOKEN=${DEPLOY_REPO/https:\/\//https:\/\/${GH_TOKEN}@} | |
echo $REPO_WITH_TOKEN | |
echo "TOKEN LENGTH: $ |