笔记库与博客迁移升级笔记(DeepSeek Harness 驱动)
本文档记录 2026-08 由 DeepSeek Harness(AI 智能体)协助完成的「Obsidian 笔记库 + 个人博客」整合迁移全过程。 从 Hexo 半成品博客 + 双文件夹维护,升级为「一个库、一键发布」的现代静态站。 当前主线:Astro 5 + XinghuisamaBlogs 主题移植版(
Blog/,已转正;Blog-Astro2/为转正前存档副本);Hexo → Fuwari → Next.js → Astro 四代演进的完整记录见下文。
一、起点与痛点
| 项 | 迁移前 |
|---|---|
| 笔记 | Zhu-OKOL-vault(Obsidian 库,fast-note-sync 自建服务器多设备同步) |
| 博客 | BlogProjects(Hexo 8 + Butterfly,部署 GitHub Pages) |
| 痛点 | 写完笔记要为博客改格式、改图片路径;两个文件夹各管一摊;博客是半成品(无评论/统计/友链/关于页) |
| 目标 | 笔记写完 → 加一行标记 → 双击一键发布;内容与工程都在一个同步文件夹里 |
二、第一阶段:合并 + 自动化发布管线(Hexo 时代)
2.1 目录整合
- 博客工程整体迁入库内
Blog/(旧版后来归档为Blog-Hexo/); - Hexo
source_dir指向库根隐藏目录.blog_staging/——生成的博客文章与图片不进 Obsidian 索引、不参与同步; - fast-note-sync 增加排除项:
Blog/node_modules、Blog/public、Blog/.deploy_git、Blog/db.json、.blog_staging。
2.2 发布管线 publish.js
- 扫描库内所有
.md,只有 frontmatter 里published: true的笔记才发布(白名单机制,随手记永不误发); - 自动处理:图片复制 + 路径改写(含空格转
%20)、封面转换、> [!NOTE]呼出框、<mark>高亮</mark>、注释删除、wiki 图片嵌入、wiki 链接转纯文本、scl代码块映射pascal高亮; - 一键脚本
发布到博客.bat:同步笔记 →hexo clean/generate→hexo deploy。
2.3 数据修复(过程中发现的历史问题)
- C语言实验报告全篇损坏:约 135 处
/assets/碎片污染(printf被切成print/assets/),逐行重建代码与 42 个图片链接; - 《我学会了如何搭建个人博客》误删:从旧版
public/渲染 HTML 逐字还原(3408 字符校验一致); - 200smart 各章
cover路径修复(去掉 Obsidian 里失效的/前缀)、第 9/10 章补齐 frontmatter。
三、第二阶段:迁移 Astro + Fuwari
3.1 为什么换
- Hexo 架构老化(EJS/Pug 模板、插件生态停滞),做现代定制别扭;
- 想要:全屏欢迎页、现代卡片、搜索、暗色、后续迁到自己服务器+域名;
- 内容与框架解耦(笔记在 Obsidian,管线可迁移),风险可控,旧工程保留作回退。
3.2 主题选择
- 调研 RyuChan(Frosti 二创)与 Mizuki(MD3 风格)后,选定 Fuwari 打底:中文支持、明暗切换、Pagefind 搜索、Expressive Code 代码块、admonition 提示框、Photoswipe 灯箱。
- 沙箱环境网络受限时的获取技巧:GitHub 直连不通 → 用
npm pack <tarball-URL>走 npm 通道下载 → 自写 PowerShell tar 解析器解包。
3.3 publish.mjs 适配要点
- Fuwari 的
published字段是日期(z.date()),YAML 里必须不带引号,否则被解析成字符串导致 schema 报错; category只支持单值,取categories最后一级;image字段即封面(/assets/...由 ImageWrapper 自动补 base);- 正文图片要显式写带 base 的完整路径(Astro 不给 Markdown 图片补 base),封面则相反(组件会补,写根相对即可)——这个不对称是踩坑试出来的。
四、第三阶段:美化与功能(参考多主题源码)
4.1 首页全屏欢迎(Butterfly full_page 思路)
- 首页横幅 100vh 全屏:大标题 + 打字机副标题(
heroTypewriter循环)+ 向下滚动箭头 + 底部波浪(SVG 动画); - 滚动折叠效果:横幅固定于视口、内容上滑盖住、欢迎文字渐隐上移,上滑恢复(对照 Butterfly
background-attachment: fixed的等价实现); - 打字机文案在
src/config.ts的heroTypewriter数组里改。
4.2 佳代子背景图系统(4+2 个槽位)
| 槽位 | 路径 | 用途 |
|---|---|---|
| 首页横幅主图 | public/images/banner.jpg |
全屏第一屏 |
| 横幅轮播 | public/images/banner-2..8.jpg |
每 10s 淡入淡出 |
| 全站背景 | public/images/background.jpg |
固定图层 + 42% 暗化 |
| 封面兜底池 | public/images/covers/ |
无封面文章按标题哈希分配 |
| 404 配图 | public/images/404.jpg |
404 页 |
| 头像 | src/assets/images/avatar.jpg |
侧边栏 + 音乐碟片封面 |
4.3 性能优化(流畅度的关键)
- 卡片毛玻璃模糊是滚动卡顿主因 → 卡片改纯半透明,模糊只留给导航栏;
- 全站背景用独立
position: fixed图层 +translateZ(0)GPU 合成,避免滚动重绘。
4.4 音乐播放器(本地音乐 + APlayer)
- 早期 MetingJS(网易云歌单)方案因 CDN/接口依赖不可靠被弃用;
- 终版:
public/music/放本地音频(歌手 - 歌名.mp3自动解析),构建时扫描成歌单; - 形态:左下角固定迷你碟片(点击播放/暂停)→ 悬停自动展开(进度/控制/歌单)→ 移开折叠;
- 模式:单个按钮三态循环 顺序 → 随机 → 单曲循环(内部驱动 APlayer 隐藏按钮切换
order/loop)。
五、踩坑记录(重要)
| 坑 | 现象 | 根因与解法 |
|---|---|---|
| 双击 bat 闪退 | 窗口一闪而过 | 脚本是 LF 换行,cmd 解析错乱;必须 CRLF + UTF-8 无 BOM |
博客图片出现 assets/assets 双层目录 |
图片 404 | 库内图片本身带 assets/ 前缀,复制时需剥掉一层 |
| 已删除文件反复"复活" | 生成的 demo 文章删了又出现 | fast-note-sync 把服务器旧状态拉回;生成目录必须加入同步排除 |
| 音乐按钮点了没反应 | 无任何报错 | 带 define:vars 的内联 <script> 被 Astro 包进函数体,import 语句成语法错误;改用「JSON 数据块 + 提升的模块脚本」模式 |
构建报 @apply link 类不存在 |
Tailwind 报错 | 跨文件 @apply 依赖处理顺序;在用到自定义类的文件里加本地副本 + @tailwind components 指令 |
| Astro 构建需子进程 | 沙箱内 EPERM | 构建器要 spawn 子进程,本地环境无此限制 |
六、当前工作流(三步)
- Obsidian 写笔记,图片直接粘贴(自动进
assets/<笔记名>/); - frontmatter 加
published: true; - 双击
Blog/发布到博客.bat:同步笔记 → Astro 构建(含 Pagefind 索引)→ 产出Blog/dist/。
- 本地预览:
Blog/本地预览.bat→ http://localhost:4321/; - 相关文档:博客发布工作流、obsidian-fast-note-sync部署笔记、
Blog/README.md。
六点五、第四阶段:XinghuisamaBlogs 主题 → Astro 5 移植(2026-08,先 Blog-Astro2 后转正为 Blog)
6.5.1 为什么再来一次
Blog-Next/(Next.js 16 + React 19 + Tailwind 4 的 XinghuisamaBlogs 本地化副本)视觉与交互最满意,但 Next 需服务器运行(API 路由/SSR),不利于纯静态部署到自己的京东云服务器;- 决策:把 XinghuisamaBlogs 的功能与视觉 1:1 移植到 Astro 静态站,保留「笔记一键发布」管线。
6.5.2 移植要点
- 组件原样搬:SplashScreen(sessionStorage 记住已看)、BackgroundSlider(10s 轮播)、Navbar(含手机轮盘)、CloudPlayer/LyricBar/SidebarLyric/FloatingPlayer、工具箱计算器等,
next/link → <a>、usePathname/useRouter → window.location/history; - next-themes 换轻量实现:BaseLayout 内联脚本渲染前读
localStorage切.dark类,ThemeToggleBlock 自给自足(不再需要 React Context); - 音乐播放器架构重写:Astro 每个 React 组件是独立 island、Context 跨 island 失效 → 把播放内核下沉为模块级单例(
src/lib/music-engine.ts,一个<audio>+useSyncExternalStore),所有播放器组件订阅同一状态;数据源按既定方案改为前端直连https://api.injahow.cn/meting/网易云接口; - 渲染管线 1:1:文章不再走 Astro 内容集合,而是
fs直读 + gray-matter + 与 Next 版完全相同的 unified 管线(remark-gfm/math、rehype-highlight atom-one-dark、rehype-katex、连续空行保护),构建时预渲染成静态 HTML——Pagefind 因此能索引到正文; - PageTransition 组件 → 纯 CSS(
.page-enter):避免把整页静态内容拖进 island 导致客户端渲染、搜索索引失效; - 新增 Pagefind 站内搜索(
/search,postbuild 自动索引 dist/,中文--language zh); - 新增标签云页
/tags(标签 + categories 最后一级分类双维筛选,/tags/<name>深链); - Gitalk 占位默认关闭:配置为空显示「留言板暂未开启」卡片,填好配置后动态加载 Gitalk。
6.5.3 踩坑记录(Astro 移植专场)
| 坑 | 现象 | 根因与解法 |
|---|---|---|
| React Context 跨 island 失效 | 播放器组件互不同步 | Astro 中每个 React 组件是独立 island;改用模块级单例 + useSyncExternalStore |
| 把整页塞进一个 island | 构建成功但 Pagefind 搜不到正文 | island 内容变成客户端渲染壳;正文必须静态渲染,交互组件用 client:load 局部挂载 |
<script src="/pagefind/..."> 构建报错 |
Astro 提示 public 资源不能被打包 | 给 script 加 is:inline,保持原样输出 |
| Vite 报"依赖无法解析" | 一连串 resolve 失败 | 实为沙箱禁 spawn(vite 要起 realpath.exe/worker 子进程);本地环境无此问题 |
src/content/ 自动生成集合弃用警告 |
构建日志提示 deprecated | 显式写 src/content.config.ts 声明 posts/chatters/moments/about 四个集合(渲染仍走 fs+unified 管线,两者互不干扰) |
| 嵌套页面相对路径写错 | Could not resolve "../components/..." |
src/pages/tags/、src/pages/chatter/ 下引用要 ../../ 起步 |
6.5.4 待办
- 部署到京东云服务器(备案域名已就绪),
astro.config.mjs的site占位要改; - 佳代子图片就位后替换
public/的 bg1~3/avatar/default-cover/favicon/404 槽位。
6.5.5 首轮实测修复(卡顿 + 播放失败)
| 问题 | 根因 | 修复 |
|---|---|---|
| 界面整体卡顿 | Tailwind 4 的 backdrop-blur-xl 引用 --blur-xl 刻度,默认 24px!全站卡片 24px 大模糊 + 原主题还强制 will-change: backdrop-filter,滚动/背景轮播时每帧重采样 |
@theme 压缩 --blur-* 全刻度(xl=6px);删掉强制 blur 与 will-change;移动端直接关闭 backdrop-filter;波形动画 height → transform: scaleY(免 layout);装饰光晕 50px 模糊 → 10px;歌词打字机 50ms → 80ms |
| 首页打开瞬间掉帧 | 13 个 island 全部 client:load 抢水合 |
首屏外的轮播/主题卡/数据面板/工具箱/返回键改 client:visible / client:idle |
| 长文打开卡 | 第 5 章 45 张图一次性全解码 | 渲染管线注入 rehype 插件:正文 <img> 全部 loading=lazy decoding=async |
| 音乐页加载很久 | meting 三个镜像串行尝试,每个 12s 超时,全挂要等 36s | 改为:先秒开本地烘焙歌单,后台 Promise.any 并行竞速 meting(6s 超时),拿到后无感替换;播放中不打断 |
| 音乐进度 00:00/00:00 不播放 | ①单例 <audio> 不再像 Next 版由 React 绑定 src,togglePlay 忘了赋值 ②烘焙歌单用的是网易云外链(http 302 且浏览器 Referer 校验) |
①togglePlay 播放前补 el.src = currentSong.src ②fetch-music.mjs 在构建期解析 302 得到 https CDN 直链再烘焙(实测无 Referer 206 流式可播);③error 事件加防死循环(全部失败则停) |
6.5.6 第二轮流畅度优化(音乐页/首页专项)
实测反馈:项目/归档/标签页流畅,音乐页特卡、首页略卡。共性在于这两页同时挂着「持续状态流 + 全屏/整卡动画」:
| 优化 | 做法 |
|---|---|
| 全屏实时模糊(音乐页头号元凶) | 视口大小的 blur(20px) filter 每次滚动都重采样 → 改为网易云 400px 缩略图铺满视口(?param=400y400),放大天然朦胧、零滤镜开销;顺带去掉全屏 backdrop-blur 遮罩与 saturate |
| 播放状态流重渲染 | timeupdate 可达 10Hz+,每次 setState 让全部播放器 island 重渲染 → 进度/时间节流到 ~4Hz,歌词变化零延迟下发 |
| 音乐页底层白做工 | 音乐页自带全屏封面背景,BaseLayout 增加 hideBackground 开关,关掉背后的全站背景轮播 |
| 整页位移进场动画 | .page-enter 从 translateY+opacity 改为纯 opacity(整页 transform 生成超大合成层) |
| 无效毛玻璃 | 被图片完全盖住的卡片(文章轮播/说说轮播/照片墙海报)去掉 backdrop-blur——模糊层被图片挡住看不见但照样每帧计算 |
| 歌单行 layout 动画 | 移除播放列表每行的 framer layout 投影(每次重渲染都做布局校验) |
| 旋转碟片 | .rotating-disc 加 will-change: transform 提前提升合成层 |
| 背景轮播 | 交叉淡入 2000ms → 1000ms(10s 间隔不变) |
经验:装饰性模糊优先用「低分辨率图放大」而不是 filter;全屏 filter 是移动端/核显的绝对禁区。
6.5.7 第三轮深度优化(首页专项 + 音乐页收尾)
| 优化 | 做法 |
|---|---|
| 播放状态流快慢分离 | 引擎状态拆慢池(歌单/歌曲/播放态/歌词/音量/模式)与快池(进度条三兄弟, 4Hz)。useMusicCore/useMusicProgress 按需订阅:播放时 4Hz 刷新不再波及 LyricBar/SidebarLyric/FloatingPlayer,只剩进度条小组件跟着动 |
| 音乐页组件树瘦身 | 进度条+控制按钮拆成独立 ProgressControls 子组件订阅快池;歌词高亮改由 currentLyric 文本推导(不再依赖 currentTime),主树只在歌词/歌曲/播放态变化时才重渲染 |
| 首页玻璃卡片去模糊 | ProfileCard/CloudPlayer/主题卡/歌词栏/数据面板去掉 backdrop-filter,改加浓半透明(bg-white/60 等)补偿可读性;毛玻璃只留给导航栏/搜索下拉/悬浮播放器 |
| 打字机降频 | 歌词打字机 80→100ms,且 >24 字的长句直接整句显示(绝大多数歌词不再触发高频重渲染) |
| 时钟停表 | SiteDashboard 用 IntersectionObserver + visibilitychange:离屏/切后台即停,不再每秒空转 |
| 头像瘦身 | avatar.jpg 2188×2188(1MB) → 512×512(35KB),每页必加载的资源瘦了 30 倍 |
| 首屏预加载 | <link rel="preload" as="image" href="/bg1.jpg">,背景首帧不和解码抢线程 |
| 轮播图懒加载 | 首页文章/说说轮播与照片墙海报的封面图加 loading=lazy decoding=async |
效果:首页播放音乐时的持续重渲染从「3 个 island × 10Hz」降到「1 个小组件 × 4Hz」;背景轮播切换时不再触发整页毛玻璃重采样;音乐页播放时 4Hz 刷新只波及一条进度条。教训:React 单例 store 一定要按变更频率分层订阅,快慢混池会让无关组件白重渲染。
6.5.8 第四轮深度优化(转正为 Blog/ + 架构级去重)
Blog-Astro2 复制转正为 Blog/(旧 Fuwari 工程移出库)。同步动作:Obsidian 排除与 fast-note-sync 排除里 Fuwari 时代的 Blog/public/assets 换成 Astro 的 Blog/public/images(否则 174 张生成图会被索引+同步上服务器),并清理已移走工程的失效条目。
| 优化 | 做法 |
|---|---|
| ClientRouter 视图过渡 | astro:transitions 的 <ClientRouter transition:animate="none">:站内跳页不再整页重载,导航栏/背景轮播/悬浮播放器/工具箱等布局 island 不重复水合,音乐引擎模块级单例全程只初始化一次(歌单不再每页重拉);内容切换用 .page-enter 纯 opacity 淡入 |
| 首页去 React 化 | ProfileCard/搜索框/文章轮播/说说轮播/主题切换卡/数据面板/开场动画/背景轮播 8 个组件全部改为「Astro 静态标记 + is:inline 原生脚本」(数据用 <script type="application/json"> 块注入,事件用 data-bound 防重绑),首页 island 从 13 → 6(其中工具箱/手机返回键还是懒水合),滚动到轮播区不再有水合卡顿 |
| 搜索页换页自愈 | PagefindUI 改为动态注入 + astro:page-load 监听 + 按元素 booted 标记,ClientRouter 下反复进出 /search 都能重新挂载 |
经验:静态站性能的终点是把"不需要共享状态"的交互全部踢出 React;Astro 的 island 是工具不是默认值——每少一个 island,每次跳页就少一次完整的水合。
6.5.9 ClientRouter 白屏事故与修复(真浏览器回归测试)
ClientRouter 上线后出现「除主页外全部白屏」。用 Playwright + Chromium 建了 scripts/nav-test.mjs 导航回归测试逐页点击定位,根因与修复:
| 问题 | 根因 | 修复 |
|---|---|---|
| 换页后白屏 | Astro 路由器 swapRootAttributes 会把新页面 <html> 的 class 整个覆盖到当前文档,抹掉 splash-seen(内容显隐钥匙)→ #app-mount-root 永久 visibility:hidden;且路由器按脚本文本内容去重,第二次进入同一页时内联脚本不再执行 |
① head 里的 splash-seen/主题恢复脚本加 data-astro-rerun(换页后强制重跑,同一任务内恢复 class,无闪屏)② 所有去 React 化组件的内联脚本加 data-astro-rerun + 已有的 data-bound 防重绑,二次进入页面功能不丢 |
| React #418 水合错配(每次进首页/音乐页报错) | 引擎模块级状态跨页存活:SSR 渲染「加载中」骨架,客户端水合时却是「已加载」→ 水合失败 | 音乐类组件(CloudPlayer/LyricBar/SidebarLyric/FloatingPlayer/MusicClient)全部改 client:only="react"——无 SSR 骨架可错配,且省掉服务端渲染成本 |
| 布局 island 换页重水合 | 路由器整体替换 body,默认不保留旧元素 | Navbar/FloatingPlayer/GlobalToolbox/MobileBackButton/背景轮播加 transition:persist(真·免水合);Navbar/FloatingPlayer 加 astro:page-load 监听手动刷新 pathname(激活态小圆点、首页隐藏悬浮球跟着路由走) |
配套产物:scripts/nav-test.mjs(Playwright 导航回归:逐页点击断言可见性/内容/主题/搜索/开场/音乐加载,20 项检查全过),跑法 node scripts/nav-test.mjs(需 npx playwright install chromium)。教训:上 ClientRouter 必须真浏览器回归——白屏在 curl/静态检查里完全看不见。
6.5.10 ClientRouter 回退,MPA 朴素深优化定版
实测 ClientRouter 在真机上仍有白屏闪烁、个别页面交互不良——回退整页导航(MPA),Blog 恢复为 Blog-Astro2 基线。保留的深优化只做「看不见的减法」,不引入任何新机制:
| 措施 | 说明 |
|---|---|
| 去 React 化(重做,不带任何路由标记) | 开场动画/背景轮播/信息卡/搜索框/文章轮播/说说轮播/主题卡/数据面板 → Astro 静态标记 + is:inline 原生脚本。首页 island 13 → 6,其余页面完全不动 |
| 保留第三轮全部优化 | blur 刻度压缩、歌词节流、快慢双池、懒加载、头像压缩、时钟停表等原样保留(都在回滚基线里) |
| 回归测试 | scripts/mpa-check.mjs(Playwright):全页导航逐页断言 + 搜索/轮播/主题交互 + 新标签直连开场 + 零 JS 错误,18 项全过 |
定版原则:每页整页加载 = 状态永远新鲜 = 没有跨页状态同步的坑。 性能靠"减少要水合的东西"而不是"加快换页的魔法"。ClientRouter 方案留档备查(6.5.9),后续若要上,必须有真机验收 + 本节的坑全部复验。
6.5.11 跨页续播(音乐不被打断)
MPA 整页导航会销毁页面里的 <audio>,播放必然中断。修复方案(在 Blog-Astro2 实现,不依赖任何路由改动):
- 播放状态接力:播放中持续把
{歌曲/进度/音量/静音/模式}写入sessionStorage(进度 1s 节流 +pagehide精确补刀); - 自动续播:新页面加载歌单后读取接力状态,切回原曲、seek 到记录位置并尝试
play()(Chrome 对"同域交互过"的会话放行,实际体验近乎无缝,实测两连跳进度 3.1s → 5.5s → 7.9s); - 手势兜底:若浏览器自动播放策略拦截(如重启浏览器恢复标签页),进入"待手势"状态,歌词栏显示
♪ 点击页面任意处继续播放 ♪(冻结歌词更新防覆盖),用户第一次点击/按键即续播(进度已 seek 到位); - 暂停语义保持:暂停后跨页仍是暂停态,不会自动开播。
回归测试 Blog-Astro2/scripts/music-continuity.mjs(Playwright,7 项):自动续播/连续两跳/暂停保持/拦截提示/手势续播/进度接续/零 JS 错误。
6.5.12 续播衔接压缩 + 悬浮播放器悬停保持
| 问题 | 根因 | 修复 |
|---|---|---|
| 悬浮歌单一移出就消失 | 播放栏与歌单面板之间有 12px 空隙,mouseleave 立即折叠 | 折叠延迟 350ms + 移入面板时取消折叠计时器(移入歌单点歌不再手忙脚乱) |
| 换页"卡一下"(衔接 ~1.5s) | 新页面的音频要等 React 水合 → 引擎启动 → 拉歌单 → 才开始缓冲 | ①接力状态增加音频直链 ②BaseLayout head 加同步预热脚本:页面解析期就创建 <audio> 并开始缓冲(不自动播放,避免从 0 响)③引擎模块导入即启动(不等水合)④引擎接管预热元素:readyState 判定后 seek + 续播,同 src 不重复赋值打断缓冲。实测衔接 1567ms → 69ms |
| 预热后 React #418 | 引擎启动早于水合,SSR"连接中"骨架与实际状态错配 | 音乐类组件(CloudPlayer/LyricBar/SidebarLyric/FloatingPlayer/MusicClient)改 client:only="react"(无 SSR 骨架可错配) |
经验:MPA 跨页续播的物理极限是"新页首包时间 + 音频首缓冲",把音频创建提前到 head 同步脚本可把感知停顿压到 100ms 内;彻底零间隙仍需 SPA,取舍由用户定。
6.5.13 方案 B 落地:iframe 外壳,真·零间隙
用户对 ~70ms 衔接仍不满意,选择零间隙方案。调研确认:预渲染(媒体加载被推迟)/Worker(无 AudioContext 输出)/SW(不能播)/弹窗(体验差) 均不可行;可行的是 SPA 或 iframe 外壳,选后者(页面 100% 保持 MPA)。
架构(htmf self-framing 思路 + 自研):
- 每个页面顶层加载时(
window.self === window.top)自动"变身外壳":把#site-content内容区替换为全屏 iframe(重新加载本页地址),音频引擎与开场动画住在外壳文档;之后所有站内导航发生在 iframe 内,外壳永不卸载 → 音乐零间隙是结构保证的; - 音乐引擎双模式:外壳=宿主(单例
<audio>+ 快慢双池 + 接力 + 跨帧广播状态/接收指令);iframe 页面=桥接(状态镜像 + postMessage 转发动作与手势),MusicProvider与全部播放器组件零改动; - 地址栏/标题由外壳监听 iframe
load同步(history.replaceState),前进后退经popstate反向驱动 iframe;深链天然支持(首次加载即变身);无 JS 环境退化为普通静态页(渐进增强); - 页面唯一的 module 脚本恰好是引擎引导(island 水合是内联经典脚本、找不到宿主自动空转),外壳无需删脚本、无需动态导入。
回归(music-continuity.mjs 扩到 16 项,Playwright):零间隙导航 27/43 个 60ms 高频采样 0 中断、0 进度回退(两连跳复验);暂停保持/拦截提示/手势续播(31s 接续)/悬停歌单保持与切歌/深链外壳变身/F5 手接续播/零 JS 错误——全过。
踩坑:①Vite ?url 导入 TS 模块产出的是 .ts 原样资产(MIME/解析靠不住),放弃动态导入,改用"页面唯一 module 脚本即引擎引导"这一事实;②烘焙歌单的网易云 CDN 令牌约 2 小时过期(403)——引擎增加"全曲失败现场重试 meting 换新链接"自救,日常由 bat 每次重烘焙兜底;③手势续播函数曾与挂起守卫互斥(attemptResume 首行 return 挡住了恢复调用),抽出 resumeFromGesture 供宿主监听与桥接转发共用。
已知取舍:每次外壳刷新(F5)仍走 ~70ms 接力;长时开站需定时重烘焙歌单(bat 流程已覆盖);无 JS/爬虫看到的是普通页面(渐进增强)。
6.5.14 换页黑闪消除(视图过渡 + 外壳背景兜底)
零间隙上线后出现「换页突然黑屏闪眼睛」:iframe 内层换页时旧文档销毁、新文档未画,间隙里露出的是外壳 body 背景——暗色模式 slate-950 接近纯黑。修复(双层):
| 层 | 做法 | 覆盖 |
|---|---|---|
| 跨文档视图过渡 | 内层 CSS 加 @view-transition { navigation: auto; }:浏览器截取旧页快照与新页交叉淡入,间隙被过渡填满,不露底 |
Chrome/Edge 111+(官方文档) |
| 外壳背景换站点氛围 | 外壳 body 换 bg1.jpg + 暗色蒙层(linear-gradient(rgba(2,6,23,.55),…), url(/bg1.jpg),经 html.shell-mode 限定只在外壳生效) |
Firefox/Safari 等不支持视图过渡的浏览器,露出的底也是站点自身背景 |
回归新增场景 7:pagereveal 事件断言内层换页时 viewTransition 存在(机制级验证)+ 外壳背景图断言,18 项全过。教训:iframe 外壳方案里"露底"是结构性的,对策是"让过渡填满间隙"(Chromium)+ "让露出的底长得像站点"(兜底)双保险。
6.5.15 跨文档视图过渡回退,双帧换页定版(彻底无闪)
视图过渡上线后换页出现随机正常/白屏/黑屏。查证为已知 bug 族:WebKit 310627「新页在过渡播放前闪烁」、280703「MPA demo 行为异常」、Chromium 41248398「换页白屏回归」;且 wpt 承认跨文档 VT 主要只测顶层上下文,iframe 内导航基本未测——正是我们的场景。结论:砍掉 VT,改结构性无闪方案。
双帧换页(不依赖任何过渡 API,全浏览器一致):
- 内层文档加捕获阶段点击拦截:同源站内
<a>(排除按钮/输入/外链/_blank/新标签组合键)preventDefault后发site:nav给外壳; - 外壳维护两个全屏帧:旧页保持可见,新页在隐藏帧加载;
load完成后换帧(旧淡出新淡入,300ms 交叉),旧帧延迟 400ms 回收;加载过慢有 2.5s 兜底; - 地址栏:外壳主动导航
pushState、帧自发导航replaceState;前进后退经 popstate 反向驱动;返回按钮(BackButton/MobileBackButton/ProfileCard)改走外壳历史栈; - 音乐引擎状态改为广播到所有帧(新帧 hello 后立即拿到全量状态);
- 修一个潜伏 bug:Astro 默认给组件
<style>加作用域后缀,JS 创建的 iframe 无该属性导致样式全失效(早期靠内联 style 掩盖)——样式块改is:global。
回归场景 7 改为「40ms 高频采样双帧透明度之和」:换页全程 minOpacitySum = 1(任意时刻至少一帧可见,露底从机制上不可能),18 项全过。定版原则:过渡类视觉方案在 iframe 内不可靠时,退回"旧画面保留到新画面就绪"这一朴素而绝对成立的方案。
6.5.16 v1.0 归档里程碑
功能与视觉 1:1、零间隙音乐 + 无闪换页两项体验指标全部达成,定版 v1.0:
- 归档:库外
D:\工业自动化笔记\blog-archive\Blog-v1.0-2026-08-16.zip——源码快照(含归档说明.md),排除 node_modules/.astro/dist/生成内容,零 Obsidian 影响; - 清理:删 continuity-out/err.txt、dist/、.astro/,
npm uninstall playwright(scripts/music-continuity.mjs18 项回归脚本保留,用时npm i -D playwright && npx playwright install chromium临时装); - 目录定局:
Blog-Astro= 归档(不再动),Blog-Astro2= 唯一工作目录,用户自行复制转正为 Blog; - README 重写:v1.0 标记 + iframe 外壳/双帧换页架构说明 + 测试用法 + 佳代子槽位表;
- Obsidian/fast-note-sync 排除已复核(node_modules/dist/.astro/src/content/posts/public/images),归档 zip 在库外。
佳代子图片槽位(找图后放入或交给我统一裁切压缩):
public/bg1~3.jpg 横构图背景轮播(宽 ≥1920、单张 <500KB)、public/avatar.jpg 方形头像(≥512)、public/default-cover.jpg 16:9 默认封面(宽 ≥1200)、public/favicon.png 图标(≥256)、public/404.jpg 404 配图。
6.5.17 佳代子图就位后的观感修正(背景静态化 + 透明度)
用户放好图后发现两个观感问题,一并处理:
- 背景轮换移除:轮播是每个 iframe 页面各自持有一份状态、各自从 bg1 开始播 → 换页/切窗必回 bg1,不协调。删
BackgroundSlider.tsx,BaseLayout背景层改渲染静态bgImages[0](纯 inline style div,还省掉一个 React island);siteConfig.bgImages只留["/bg1.jpg"](想恢复轮播:加回 URL + 从 v1.0 归档还原组件)。 - 透明度两连修:① 发现隐蔽 bug——压暗蒙层
z-[-9]/渐变z-[-8]负 z-index 排在背景图下方,蒙层从未真正盖在图上(这就是「面板和背景打架」的根因),改为不带负 z、靠 DOM 顺序自然叠加,蒙层加浓为bg-white/55 dark:bg-slate-950/70;② 全站玻璃面板透明度集体上调一档(脚本单遍替换:bg-white/30~70 → /55~85、dark:bg-slate-800/900同步、bg-slate-50/100 内衬上调、Pagefind 搜索卡 0.5→0.75),外壳暗色蒙层 0.55→0.65。 - 替换按「降序处理 + 新值不与后续源值撞车」保证零重复叠加,替换后 grep 复核无残留。
6.5.18 背景蒙层回调 + 正式上线 zhu-okol.cn(京东云)
- 蒙层回调:整页蒙层太暗(55/70),用户反馈「只调框里就行」——面板保持 55~85 不透明不变,整页蒙层降为
bg-white/10 dark:bg-slate-950/30(保留层级修复:蒙层在背景图之上,只给露在卡片外的文字兜底)。 - 部署上线(一步到位,含 HTTPS):
- 环境:CentOS 7 + nginx 1.20.1 + SELinux Disabled;
astro.config.mjssite →https://zhu-okol.cn,siteConfig填备案号「沪ICP备2026035957号-1」+ 友链申请格式换正式域名; - 文件放
/var/www/zhu-okol/(本机 tar 打包 → ssh2 SFTP 上传 → 远程解压,70MB 一次传完); - nginx:
conf.d/zhu-okol.conf(静态资源分级长缓存 +try_files $uri $uri/ $uri.html =404+ 404 页 internal);certbot --nginx 自动写入 443 块并 301 跳 https;证书 Let's Encrypt(到期 2026-11-14),/etc/cron.d/certbot-renew每日 3 点自动续期; - 两个坑:① CentOS 包自带 nginx.conf 的默认站位于
include conf.d/*.conf之后,导致 conf.d 里的 80 块成为隐式默认站——IP 直访落到return 404,修复 = 给 nginx.conf 默认站显式加default_server并 301 跳主站;②try_files兜底/404.html会以 200 状态吐 404 页,改=404+location = /404.html { internal; }后返回真实 404; - 上线验证全过:外网 http 301 → https 200、文章深链 200、pagefind 200、不存在页面 404、页脚备案号在位;
- 新增
部署到服务器.bat(同步歌单→同步笔记→清空重建 dist→打包→上传→重载),上传凭证在库外deploy-tools/;三个构建类 bat 全部加rmdir /s /q dist防旧文件残留。 - 待办:公安网备案(beian.mps.gov.cn,ICP 通过后 30 天内建议办理)。
- 环境:CentOS 7 + nginx 1.20.1 + SELinux Disabled;
6.5.19 命名定案 + 换歌 + 部署脚本幂等化
- 命名定案:英文 Zhu-OKOL / Zhu_OKOL(标题/社交/GitHub/域名),中文显示层 佳伟(
siteConfig.authorName全局替换,页脚/关于页/文章作者框/说说/开场都变),平台昵称「佳伟_佳代子单推人」;站点标题保持Zhu-OKOL の 自动化笔记。 - 换第一首歌:
cloudMusicIds[0]1809646618 → 2098477275(Aira - ミツキヨ);注意歌单 ID 有两处——siteConfig.ts(运行时 meting)与scripts/fetch-music.mjs(构建期烘焙),注释已互相提醒同步。 - 部署脚本幂等化(重要教训):
deploy.mjs原来每次都重写 nginx 配置——第二次部署覆盖掉 certbot 写入的 443 块、又重写 default.conf 与 nginx.conf 的 default_server 冲突 →nginx -t直接报 duplicate default server,配置进入损坏状态。修复:站点 conf 仅在不存在时写入(之后交给 certbot 管理)、不再碰 default.conf、HTTPS 自检改正确 SNI;全流程重跑一遍验证无误。结论:部署脚本对服务器配置必须幂等,站点文件随便覆盖、nginx 配置只写一次。
七、后续计划
- 迁移到自建服务器 + 备案域名(已完成:https://zhu-okol.cn,nginx + HTTPS 自动续期,
部署到服务器.bat一键上线) - 公安网备案(beian.mps.gov.cn);
- 佳代子图片就位后整体观感微调(bg1/头像/默认封面/favicon/404 已就位,剩余 bg2/bg3 备用);
- 稳定优化路线图(构思中,本版不实施):Media Session API 接管系统媒体键、hover 预建 pending frame 提前加载下一页、发布管线图片压缩瘦身、外壳记住每帧滚动位置等;
- 留言板换自建 Twikoo(服务器已就绪);
- 学习路线页(把三年计划做成站点导航);
- 访问统计、友链内容填充。