DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

gxpppp /

gxpppp/dsh-boot-anim

Verified

DSH 桌面版启动动画(SteamOS 风格七段分镜)+ UI 由外向内分层入场过渡。宿主侧插件,经 webserver/index-inject 注入,零依赖、零源码改动。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@17015b54

DSH 桌面版启动动画 + UI 入场过渡

SteamOS 风格的七段启动分镜,以及 DSH 桌面端 UI 的「由外向内、环环平移」入场过渡。 零依赖 · 零源码改动 · 可一键还原。

启动动画演示

↑ 黑屏 → 鲸鱼 → 双线内合 → 轮廓勾勒 → 中线展开 → 落白 → UI 环环入场(约 5.6 秒,此处 10fps 采样)


目录

  • 为什么这样做
  • 七段分镜
  • 快速开始
  • 鲸鱼轮廓
  • UI 入场过渡
  • 动效令牌
  • 安全网与降级
  • 文件结构
  • 开发
  • 已验证 / 未验证
  • 参考来源
  • 许可

为什么这样做

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()
  │    只在 <head> 里塞一行 __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 只是掌机档位。


快速开始

一条命令安装(推荐)

dsh plugin --profile desktop add github:gxpppp/dsh-boot-anim

然后把包名加进 profile 的 dsh.profile.bundles(安装命令只写依赖,不改 bundles):

"dsh": { "profile": { "bundles": [ /* …既有… */ , "dsh-boot-anim" ] } }

重启「DeepSeek Harness」桌面版即可。

为什么还要手动加 bundles:dsh plugin add 只把包装进 node_modules, 而插件要生效必须出现在 dsh.profile.bundles 里。这是 DSH 当前的行为,不是本插件的要求。

从本地目录安装(改代码即时生效)

适合自己改动画、不想每次 commit 的场景:

git clone https://github.com/gxpppp/dsh-boot-anim.git
cd dsh-boot-anim
powershell -ExecutionPolicy Bypass -File install.ps1

脚本做三件事——加 link: 依赖、追加到 bundles、建 junction 到 node_modules。 因为用的是 junction,改完 lib/ 下的文件刷新页面即可看到效果,不必重新安装。

卸载

powershell -ExecutionPolicy Bypass -File uninstall.ps1

用 dsh plugin add 装的,则用 dsh plugin --profile desktop remove dsh-boot-anim, 并自行从 bundles 里删掉包名。

包名必须与仓库名一致

这不是洁癖,是硬约束:加载器的 resolveBundleDir() 按 dsh.profile.bundles 里的名字 去 node_modules 找包。名字对不上就会被丢进 skippedBundles —— 静默跳过,界面无任何提示。

所以 package.json 的 name 是 dsh-boot-anim,仓库名也是 dsh-boot-anim。


鲸鱼轮廓

需求要求轮廓与现有图标一致,做法是直接把原 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,#ba-clip-r 只留 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,落在「总交错 < 800ms」的通行区间内。

一个会静默失效的坑

[data-slot] 锚点的样式是 display: contents ——

/** Anchor style shared by every outlet wrapper: display:contents keeps the
  * wrapper out of layout ... so the anchor is purely addressable surface. */
const ANCHOR_STYLE = { display: "contents" };

没有盒子,getBoundingClientRect() 返回全 0。拿它做动画目标等于空操作, 而且不报错。本实现用 collectBlocks() 穿透这类中间层往下钻,只收「有盒子且够大」的元素。


动效令牌

所有曲线与时长集中在 lib/boot-anim.js 顶部的 T 表,每一项目标都有出处:

var T = {
  ENTER:  'cubic-bezier(0.23, 1, 0.32, 1)',   // emilkowalski/skills 的 --ease-out
  IN_OUT: 'cubic-bezier(0.77, 0, 0.175, 1)',  // 同上的 --ease-in-out
  DRAW:   'cubic-bezier(0.22, 1, 0.36, 1)',   // mblode/agent-skills 的 Enter
  // …
}

采用的三条硬规则:

  • 进场一律 ease-out 家族,绝不用 ease-in —— 后者起步慢,正好拖慢用户最关注的那一刻。
  • 只动 transform 与 opacity —— 两者都在合成层,不触发布局与重绘。
  • 数值不凭手感编 —— 每个曲线和时长都来自 参考来源 里的表。

想调快调慢,改这个表即可,不必翻实现。


安全网与降级

情形 行为
prefers-reduced-motion: reduce CSS 里 display: none !important;JS 里直接结束,不播放
用户在动画中按键 / 点击 / 滚轮 立即跳到揭幕,不阻断操作
任何一环抛异常 finally 里必定清理,界面绝不永久压在黑幕下
总时长超 20 秒 兜底定时器强制跳过
Element.animate 不可用 逐项跳过,不卡住
注入晚于 React 首次挂载 舞台 z-index 极高 + 等待 UI 就绪,最差是 UI 闪一帧后被覆盖

终态没有任何 DOM 残留(舞台整体移除),露出的是真实 UI 本身, 所以「露出的皮肤」在构造上就等于「已装配的皮肤」,不存在样式回退问题。 颜色只从 var(--dsw-alias-bg-base, #fff) 取,不硬编码。


文件结构

dsh-boot-anim/
├── package.json                 插件包声明(dsh.bundle.patch)
├── cordis.patch.yml             loader patch:insert 进 desktop profile
├── install.ps1 / uninstall.ps1  安装与卸载
├── verify-headless.ps1          一键无头复现
├── LICENSE  CHANGELOG.md  SECURITY.md
├── .github/
│   ├── workflows/verify.yml     语法 / 单测 / 结构 / 敏感信息
│   ├── ISSUE_TEMPLATE/
│   └── PULL_REQUEST_TEMPLATE.md
├── lib/
│   ├── index.js                 宿主半侧:订阅 webserver/index-inject
│   ├── boot-anim.css            舞台样式(作用域前缀,不碰应用类名)
│   └── boot-anim.js             前端半侧:七段分镜 + 三段波次(零依赖 IIFE)
├── docs/
│   ├── 01-方案检索清单.md        检索到的方案与来源链接
│   ├── 02-UI入场动画技术对比.md  六种挂钩方式对比与本机实测
│   ├── 03-技术选型与实现.md      选型理由与改动清单
│   ├── 04-验证结果.md            时序实测、逐帧截图、未验证项
│   ├── media/                    演示 GIF / 视频 / 封面
│   └── _verify/                  技术对比文档的验证资产
└── _verify/
    ├── run.mjs                   无头逐帧采样
    ├── record.mjs                录制演示视频
    ├── test-host.mjs             宿主半侧单测
    ├── probe-loader.mjs          用真实加载器验证 bundle 被接受
    ├── probe-e2e.mjs             用真实 cordis 跑端到端
    ├── check-structure.mjs       结构完整性 + 敏感信息扫描
    ├── render-all-frames.mjs     全量逐帧渲染(供逐帧检视)
    ├── gen-wordmark.mjs          从上游提取官方字标矢量
    ├── diag-sheen.mjs            两层几何是否重合
    ├── diag-opacity.mjs          压暗是否生效
    ├── diag-progress.mjs         鲸鱼与字标是否重叠
    └── fish-logo-path.txt        鲸鱼 path 校验锚点

开发

# 语法检查
node --check lib/index.js && node --check lib/boot-anim.js

# 宿主半侧单测
node _verify/test-host.mjs

# 结构与敏感信息检查(CI 同款)
node _verify/check-structure.mjs

# 用真实加载器验证 bundle 能被解析
ELECTRON_RUN_AS_NODE=1 "<DSH>/DeepSeek Harness.exe" _verify/probe-loader.mjs

# 用真实 cordis 跑端到端
ELECTRON_RUN_AS_NODE=1 "<DSH>/DeepSeek Harness.exe" _verify/probe-e2e.mjs

两个探针需要显式给出路径(本机会把 HOME / USERPROFILE 改写):

$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 节。


许可

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 17015b543733

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout