@local/dsh-boot-anim
DSH 桌面端启动动画(SteamOS 风格分镜)与 UI 分层入场过渡。纯宿主侧 index 注入,无 HTTP 路由、无源码改动。 适合追求极客启动动效、想要美化桌面端软件加载过程的用户。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:gxpppp/dsh-boot-animREADME
Read the full README ↗DSH 桌面版启动动画 + UI 入场过渡
SteamOS 风格的七段启动分镜,以及 DSH 桌面端 UI 的「由外向内、环环平移」入场过渡。 零依赖 · 零源码改动 · 可一键还原。

↑ 黑屏 → 鲸鱼 → 双线内合 → 轮廓勾勒 → 中线展开 → 落白 → UI 环环入场(约 5.6 秒,此处 10fps 采样)
目录
为什么这样做
DSH 桌面版运行时只加载 app.asar;resources/app/ 那个看似「解包副本」的目录根本不会被加载。
所以能改的地方只有三条路,本方案选第三条:
| 候选 | 为什么不选 |
|---|---|
解包重打包 app.asar | 每次 DSH 自动更新都会覆盖;121 MB 重打包有损坏安装的风险;难以干净回滚 |
改 resources/app/ | 该目录是陈旧副本,改了完全不生效 |
| 宿主侧插件 + index 注入 ✅ | 走 DSH 官方插件契约,更新后不失效;不碰任何原始文件;卸载即还原 |
决定性的发现是桌面版加载链路的一个特点:
resources/app.asar/lib/main.js
├─ serveWebDocument()
│ 直接 readFile(dist/index.html),不调用 renderIndex()
│ 只在 里塞一行 __DSH_BOOT_READY__
└─ DESKTOP_IPC.boot 返回 { injections, streamBaseUrl }
↑ 值来自 ctx.webServer.collectIndexInjections()
桌面版虽然不把注入行渲染进 HTML,但会把同一张行表通过 IPC 交给页面,
由前端解释器逐行执行。因此只要往 collectIndexInjections() 的表里 push 行,
注入就行之有效——无论内容多长,也无论有没有 HTTP 路由。
这就是宿主半侧只有 30 行的原因。
七段分镜
| # | 画面 | 实测时间窗 |
|---|---|---|
| ① | 黑屏 | 0 – ~0.5s |
| ② | 屏幕中央出现鲸鱼 | ~0.2 – ~0.9s |
| ③ | 转全黑,两条线由左右两侧向中间延伸 | ~0.95 – ~1.4s |
| ④ | 两线构筑成鲸鱼,并勾出与侧边栏图标一致的轮廓 | ~1.4 – ~2.15s |
| — | 收笔后停住,等应用真正挂载 | ~2.15 – ~3.3s |
| ⑤ | 中央横线向上下两侧展开,画面一分为二 | ~3.3 – ~4.2s |
| ⑥ | 展开同时鲸鱼缓缓消失,露出默认白底 / 已装配皮肤 | ~3.45 – ~4.2s |
| — | 缓冲过场:鲸鱼重组左移 → 官方字标浮现 → 光带等待后端 | ~2.2 – ~4.2s |
| ⑦ | UI 以由外向内、环环平移的方式入场 | ~4.3 – ~5.3s |
| 总时长 | ≈ 5.6s |
第 ④ 段收笔后不立刻揭幕,而是等应用真正挂载(最长 9 秒)。 这样慢启动不会「动画放完露空白」,快启动也不会「UI 早好了还压着黑幕」。
时长取 5.6 秒而非掌机档的 4 秒,依据是本机 Steam 客户端实测:官方桌面场景开机动画
steam_os_startup.webm = 5.000s / 1920×1200,而 deck_startup.webm 的 4.008s 只是掌机档位。
快速开始
安装
# 在本仓库根目录执行
powershell -ExecutionPolicy Bypass -File install.ps1
然后重启「DeepSeek Harness」桌面应用。
脚本会:备份 profile 的 package.json → 加一条 link: 依赖 → 往 dsh.profile.bundles 追加本包 →
在 node_modules/@local/ 建 junction。它不触碰 DSH 安装目录。
可用参数覆盖路径:
.\install.ps1 -PluginDir "D:\somewhere\dsh-boot-anim" -ProfileDir "$env:USERPROFILE\.dsh\profiles\desktop"
卸载
powershell -ExecutionPolicy Bypass -File uninstall.ps1
不重启也能看动画
无头 Chromium 跑的是同一份 CSS / JS:
powershell -ExecutionPolicy Bypass -File verify-headless.ps1
需要 Playwright。可用 -PlaywrightEntry 或 DSH_PLAYWRIGHT 环境变量指定它的位置:
.\verify-headless.ps1 -PlaywrightEntry "D:\path\to\playwright\index.mjs"
逐帧截图会写到 _verify/f*.png。
鲸鱼轮廓
需求要求轮廓与现有图标一致,做法是直接把原 path 抄过来,不是照着重画:
- 来源:
@deepseek-ai/dsh-client-ui-primitives的FISH_LOGO_PATH - 规格:3448 字符;4 条子路径(
M…C…Z);viewBox="0 0 23.16 17.04" - 校验锚点:
_verify/fish-logo-path.txt保存逐字节副本,CI 会核对二者一致
侧边栏图标与对话区 hero 用的是同一个 FishLogo 组件,所以「与图标一致」是构造性成立的。
两个几何上的取舍
同一段 path 画两遍。 分镜 ④ 要让「两条线分别构筑」,做法是把同一段 d 渲染两次,
各套一个 clipPath:#ba-clip-l 只留 x 11.58。
两条路径的 stroke-dashoffset 反向(-1 → 0 与 +1 → 0),
于是描边从中间同时向两端铺开。
压扁时描边不能变细。 ③ 与 ④ 需要同一个元素从「一条横线」连续变形成鲸鱼轮廓:
鲸鱼组套 scaleY(0.018) 压成约 2px 的横线,再 scaleY: 0.018 → 1 长成鲸鱼。
但压扁会把描边一起压细到看不见——解法是 vector-effect: non-scaling-stroke,
描边宽度不随 transform 缩放,压平时它仍是一条实心横线。这样 ③ 与 ④ 不必切换 DOM。
缓冲过场
在「轮廓勾勒完成」与「横线展开」之间插了一段过场:鲸鱼淡出重组、左移让位, 官方字标在其右侧浮现;若后端仍在加载,一道斜向光带反复扫过字面。
字标要等鲸鱼左移进行到 82% 才浮现 —— 早于此时会被还停在中央的鲸鱼压住, 两者都是矢量、会直接叠在一起。这是逐帧实测出来的,不是估的。
字标用的是官方矢量
不是文字排版,而是直接从上游 dsh-client-ui-primitives 的 BrandWordmark
组件提取的矢量数据:18 个图元 / 13931 字符 path / 2 处裁剪框,
由 _verify/gen-wordmark.mjs 自动生成并断言结构。手抄这 1.4 万字符不可能不出错。
数据经 global 注入行送进页面 —— boot-anim.js 是注入的独立脚本,无法 import。
光带的实现
光带层是字标的同形副本,被一道移动的遮罩裁切:只有光带扫过的部分才高亮出来。
这里有两个坑,都是实测才发现的:
- 不能用
mix-blend-mode: screen—— 屏幕混合下「白叠白」恒为白,等于没有效果; - 定位必须与字标层完全一致 —— 曾因写成
inset: 0而铺满容器, 导致字标副本被放大 3.46 倍、跑到左上角(看起来像两个巨大的汉字)。
另外「加载期间压暗字标」也踩过一次:内联样式会被入场动画的
fill: 'forwards' 终值覆盖(实测 inline 0.22 而 computed 1),
必须改用 WAAPI 动画才能压下去。
详见 docs/04 第 9 节。
UI 入场过渡
没有稳定类名怎么找 UI 分区
DSH 前端是 CSS Modules(hash 类名)+ 打包产物,没有可挂钩的类名。链条是:
document.querySelector('[data-shell-overlay]') ← AppFrame 里唯一的语义锚点
.parentElement ← 就是 AppFrame 的三列 grid 容器
.children → 按 getBoundingClientRect().left 排序
→ 最左 = 侧边栏、中间 = 对话列、最右 = 右栏
三段波次
| 波次 | 目标 | 方向 | 位移 | 时长 | 交错 | 起始延迟 |
|---|---|---|---|---|---|---|
| 1 | 外层三列 | 左栏向左、右栏向右、中列向上 | 40px | 460ms | — | 0 / 70 / 140ms |
| 2 | 列内主块 | 自下浮起 | 26px | 380ms | 60ms | — |
| 3 | 主块内内容块 | 自下浮起 | 16px | 320ms | 40ms | — |
合计约 730ms,落在「总交错 /DeepSeek Harness.exe" _verify/probe-loader.mjs
用真实 cordis 跑端到端
ELECTRON_RUN_AS_NODE=1 "/DeepSeek Harness.exe" _verify/probe-e2e.mjs
两个探针需要显式给出路径(本机会把 `HOME` / `USERPROFILE` 改写):
````powershell
$env:DSH_RESOURCES = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources"
$env:DSH_PROFILE = "$env:USERPROFILE\.dsh\profiles\desktop"
已验证 / 未验证
已验证(无头 Chromium 154,跑的是即将上线的同一份 CSS / JS):
- 七段分镜顺序与时序,18 个采样点覆盖全部镜头
- 第 ④ 段鲸鱼轮廓可辨识,与侧边栏图标是同一条 path
- 舞台自毁、无 DOM 残留、无标记残留、控制台零报错
- UI 三段波次目标数
{cols: 3, inner: 5, deeper: 2} - 真实加载器:25 个 bundle 全部接受、0 跳过
- 真实 cordis 端到端:12/12 断言通过
- 安装 / 卸载往返:profile 正确改写与回滚,无 BOM
未验证(如实标注,不从等价验证外推):
- 真实 DSH 桌面窗口中的最终观感 —— 需重启桌面版
- 真实桌面版的 IPC 往返时机 —— 同上
参考来源
完整清单见 docs/01-方案检索清单.md。核心来源:
- mblode/agent-skills · ui-animation —— 动效决策框架、编排、SVG 线描(含
pathLength/transform-box/ round cap 陷阱) - emilkowalski/skills · animate —— 入场曲线、UI 动画 <300ms、禁止 ease-in
- joepUI/motion-ref-skill —— 横向滑入与 stagger 配方
- LottieFiles/motion-design-skill —— 时长表与 stagger 预算
- Jake Archibald · Animated line drawing in SVG
- CSS-Tricks · How SVG Line Animation Works
- Christian Engvall · Electron white screen app startup
SteamOS 侧的一手数据来自本机 Steam 客户端的启动动画文件元数据实测,见 docs/01-方案检索清单.md 第 0 节。