# DSH 桌面版动画方案检索清单 **检索目标**:为 DeepSeek Harness 桌面版(Electron + React 单页应用 + CSS Modules)设计两套动画 —— ① SteamOS / Steam Deck 风格启动动画;② 各面板首次加载「由外向内平移入场」过渡动画。 **硬约束**:只用 CSS3 / SVG / Web Animations API,不引入新的运行时依赖。 **检索时间**:2026-10-03(Asia/Shanghai)。**检索方式**:逐条实际抓取(GitHub 网页版返回的是 JS 导航壳,故全部改抓 raw.githubusercontent.com / api.github.com 原文);本机 Steam 客户端安装目录亦作为一手样本读取。 **阅读约定**:凡标注「实读」的条目,均给出抓取到的字符数与原文摘录;凡标注「抓取失败」的,在 §D 统一列出并给出替代来源。本文不收录仅凭搜索摘要得出的结论。 --- ## 0. 一手实测基准:本机 Steam 客户端的官方启动动画 本次检索最硬的一条证据不在网上,而在本机 —— Steam 客户端内置了官方启动动画文件,可直接读取其容器元数据。 **样本路径**:`C:\Program Files (x86)\Steam\steamui\movies\` | 文件 | 字节数 | 编码 | Track 分辨率 | 时长 | | --- | --- | --- | --- | --- | | `deck_startup.webm` | 1840847 | V_VP9 | 450×450 | 4.008s | | `oled_startup.webm` | 764649 | V_VP9 | 450×450 | 4.000s | | `startup_machine.webm` | 320937 | V_VP9 | 1920×1080 | 3.117s | | `bigpicture_startup.webm` | 1338011 | V_VP9 | 1920×1200 | 6.000s | | `deck-suspend-animation.webm` | 160008 | V_VP9 | 450×450 | 1.000s | **测量方法**(可复现):直接扫描 WebM/Matroska 的 EBML 元素 —— `Duration`(id 0x4489,Segment/Info 内,TimecodeScale=1ms)、`PixelWidth`(0xB0) / `PixelHeight`(0xBA)(Video 元素内)、`CodecID`(0x86)。 **交叉验证**:① `bigpicture_startup.webm` 的扫描结果 1920×1200 与 Windows Shell 媒体属性读取值完全一致,证明扫描法有效;② `deck_startup.webm` 的 Duration 原始值 4008 与 Shell 读取的 4.008s 一致。 **由此得到的三条工程事实**: 1. **官方开机动画的时长基准 ≈ 4.0 秒**,且 LCD(deck_startup)与 OLED(oled_startup)两代完全一致 —— 4.008s 与 4.000s。这不是「越长越炫」的路线,而是一个约 4 秒的确定时长。 2. **官方开机动画的 Track 是 450×450 的方形容器**(两代一致),并非屏幕原生的 1280×800 —— 说明播放层存在缩放行为。社区替换经验要求「1280×800、16:10」(见 A3),两者差异说明:**自绘启动动画应按窗口/屏幕宽高比制作,而不是照抄官方容器的方形比例。** 3. **1840847 字节 = 社区流传的 truncate 目标值**(见 A2),与官方 `deck_startup.webm` 的实际字节数完全吻合 —— 印证了社区替换手法确实是在「等长覆盖官方文件」这一机制上运作的。 **主 Agent 复核补充(2026-10-03,独立复扫同一目录)**:主 Agent 用自写的 EBML 扫描器把该目录**全部 22 个 webm** 重扫了一遍,复现了上表全部数值,并发现子代理漏扫的 3 个**与「开机动画」语义更直接相关**的官方文件: | 文件 | 字节数 | 分辨率 | 时长 | | --- | --- | --- | --- | | `steam_os_family_startup.webm` | 733124 | 1024×1024 | **8.948s** | | `steam_os_startup.webm` | 332642 | 1920×1200 | **5.000s** | | `steam_os_suspend.webm` | 74707 | 450×450 | 2.000s | **这三条修正了原结论的适用范围**:`4.008s / 4.000s` 是 **Steam Deck 掌机**的开机动画时长,**不是 SteamOS 的通用规格** —— Steam 客户端在大屏场景下的开机动画是 **5.000s(`steam_os_startup`)**,家族级启动片头甚至到 **8.948s**。同时也可见官方在**同类动画上按场景给出多个时长档**(1.0s 挂起 / 3.0–4.5s 控制器演示 / 4.0s 掌机开机 / 5.0s 桌面开机 / 8.9s 家族片头)。 **对 DSH 的意义**:Valve 并非「所有启动动画都 4 秒」,而是**按场景分档**。DSH 桌面版属桌面大屏场景,参照档位应是 **`steam_os_startup` 的 5.0s 量级**,而不是掌机的 4.0s。本实现取 **约 5.6s(不含等待 UI 就绪的时间)**,落在该量级内。 --- **未取得的部分(如实说明)**:官方动画的**逐帧画面内容**未能确证。本机未安装 ffmpeg / ffprobe,Python 侧无 imageio_ffmpeg / cv2 / av(实测探测结果:`{'imageio_ffmpeg': False, 'cv2': False, 'PIL': True, 'numpy': True, 'av': False, 'moviepy': False, 'imageio': False}`),无法抽帧;Steam 官方页面、YouTube、Reddit 在本机网络下抓取失败。因此本文只陈述容器级事实(时长 / 编码 / 分辨率 / 字节数),**不对画面构图做任何未经来源支撑的描述**。需要看画面时,直接打开上面那个目录即可。 --- ## A. SteamOS / Steam Deck 开机动画参考 ### A1. Valve 官方开机动画(本机安装样本) - **名称**:Steam 客户端内置启动动画(`deck_startup.webm` 等 5 个文件) - **来源**:本机路径 `C:\Program Files (x86)\Steam\steamui\movies\`(非 URL;一手实测,方法与结果见 §0) - **它实际的视觉语言**:仅能确认容器事实 —— 4.008s / VP9 / 450×450 / 1.84 MB。**画面内容未取得可引用来源,不作描述。** - **可复用于本任务的点**: - 时长锚点:把 DSH 启动动画目标定在 **3.5–4.5s**,与 SteamOS 的节奏对齐,而不是 8s 以上的「长片头」。 - 编码无关:DSH 用 CSS/SVG 自绘,不需要视频容器;但**「4 秒 + 一次性播放 + 播完即让位」**的行为模型可以整体照搬。 - 交互模型:启动动画是「独占全屏层」,结束后把控制权交给主 UI(见 A6 的时序证据)。 ### A2. GamingOnLinux —《Custom boot animations for the Steam Deck are pretty sweet》 - **URL**:https://www.gamingonlinux.com/2022/09/custom-boot-animations-for-the-steam-deck-are-pretty-sweet/ - **状态**:实读(正文 14488 字符,已剥离导航噪声) - **关键原文摘录**: - `you need a .webm video file of an animation, whatever you want` - `Head to .local/share/Steam/steamui/movies/ and make a backup of deck_startup.webm` - `truncate -s 1840847 deck_startup.webm`(用官方文件的实际字节数覆盖新文件) - `Open the library.css file, go to the line that has "video" and edit just after it to read like this: video{flex-grow:1;width:100%; height:100%; z-index:10}`,随后 `truncate -s 38492 library.css` - **它实际的视觉语言**:该页只讲替换方法,**未描述默认动画的画面**(如实说明)。 - **可复用于本任务的点**: - 官方对启动动画的工程约束是**「替换式资源 + 固定占位尺寸」**,而不是开放 API:文件必须与原文件等长(truncate 到同一字节数),视频元素需要一条 CSS 规则撑满全屏(`width:100%; height:100%`)。 - 映射到 DSH:启动动画应做成**独立的一层全屏容器(loading layer)**,其尺寸策略是「占满窗口」,与主 UI 解耦;即使在白屏/首帧阶段也能独立渲染。这正好和 C2 的 Electron 白屏规避是同一件事的两面。 - `z-index:10` 说明官方把它压在 UI 之上 —— 对应 DSH 里启动动画层应位于应用根节点的最上层,动画结束后卸载或淡出。 ### A3. crstian19/Steam-Deck-Boot-Animations(社区整理仓库) - **URL**:https://github.com/crstian19/Steam-Deck-Boot-Animations (实读 raw README,3152 字符) - **关键原文摘录**: - `You want to grab the video. make sure it's a webm file, 1280x800, 16:10 or it won't fit.` - `Navigate to .local/share/Steam/steamui/movies` - `truncate -s 1840847 (filename.webm)` - `video{flex-grow:1;width:100%;height:100%;z-index:10}` + `truncate -s 38492 library.css` - **可复用于本任务的点**: - **1280×800 / 16:10 = Steam Deck 屏幕比例**。这条与 §0 的 450×450 容器实测并列读,得出的结论是:**动画内容要按显示区域的宽高比设计,缩放由播放层负责。** DSH 的自绘动画应使用百分比 / `viewBox` / `clamp()` 等相对单位,而非固定像素画布。 - 社区替换的核心手法(等长覆盖 + CSS 覆盖)说明官方播放层是稳定的、版本化的 —— DSH 若把启动动画做成可替换资源,也应遵循「原资源只读、覆盖走独立目录」的分层(见 A5)。 ### A4. TheLogicMaster/SDH-AnimationChanger(Steam Deck 动画替换插件,★110) - **URL**:https://github.com/TheLogicMaster/SDH-AnimationChanger (实读 raw README,2620 字符) - **关键原文摘录**: - `This is a Steam Deck Homebrew plugin for easily changing boot/suspend animations and randomizing on boot.` - 三类动画的默认文件名(`config.json` 示例):`"boot": "deck_startup.webm"`、`"suspend": "deck-suspend-animation.webm"`、`"throbber": "deck-suspend-animation-from-throbber.webm"` - `"boot" refers to the main boot animation, "suspend" refers to the suspend animation played when not in a game, and "throbber" refers to the animation played when suspending from in-game.` - `Animations get enabled by creating symlinks in the override directory, ~/.steam/root/config/uioverrides/movies` - **可复用于本任务的点**: - **动画按「时机」分类**而不是按「样式」分类:冷启动 / 非游戏挂起 / 游戏内挂起。映射到 DSH,可预先定义三档:`boot`(进程启动)、`first-paint`(首屏面板入场)、`idle`(空闲/加载中),共用一套时长与缓动令牌。 - **符号链接切换 + config.json 清单**:资源不改动、只改指向。DSH 若要支持换肤/换动画,这是比「改打包资源」更干净的方案。 ### A5. DRok17/Steam-Deck-BootVid-Builder(★34) - **URL**:https://github.com/DRok17/Steam-Deck-BootVid-Builder (实读 raw README,8846 字符) - **关键原文摘录**: - `Valve has now introduced official support for Overrides. Files will be placed the following directory:` - `/home/deck/.steam/root/config/uioverrides/movies/` - 流程:`WEBM-Input` → 运行 `Add-Video-2-Library.sh` → `Vids`/`Suspends` 目录 → `z-prep-apply-boot.sh` - **可复用于本任务的点**: - 官方提供的是**用户覆盖目录(uioverrides)**,原始资源只读 —— 这正是 DSH 该采用的资源分层:`内置动画资源(只读) ← 用户覆盖目录`。 - 「输入 → 库 → 应用」三段式脚本流程,可以对应 DSH 的「动画源文件 → 校验/预处理 → 生效」管线;本轮只需把覆盖目录预留出来。 ### A6. ValveSoftware/steam-for-linux 的两个 issue:动画 → UI 的过渡如何发生 - **URL**: - https://github.com/ValveSoftware/steam-for-linux/issues/11655 (实读 API 正文) - https://github.com/ValveSoftware/steam-for-linux/issues/11715 (实读 API 正文) - **关键原文摘录**: - #11655(SteamOS 3.6):`I can see black screen for the first 2/3 seconds and sound of boot video in the background. After that, I see on the screen the end of animation. Only the default boot video works properly` - #11715(SteamOS 3.6.20,LCD):`Custom startup movie doesn't play when restarting. Valve built-in startup movie does play on restarts, but not custom. … You can hear the movie's audio playing only the external monitor screen is black.` - **这两条说明了什么(区分事实与推断)**: - **事实**:启动视频的音轨会先于画面输出(「只听到声音、屏幕黑」);视频播放层与 UI 输出层是**相互独立、并行初始化**的;默认动画在同样条件下能正常显示,自定义动画更容易踩到这个窗口。 - **推断(标注为推断)**:SteamOS 3 的「动画结束 → 桌面出现」**不是交叉淡出**,也没有可感知的统一过渡层 —— 视频播完(或被打断)后直接露出 Gamepad UI,表现为硬切;用户感知到的「黑屏 2–3 秒」正是视频层已就绪、而合成/输出层尚未就绪的窗口。 - **另一条推断**:动画时长与 UI 就绪时间存在竞争关系 —— 若 UI 早于动画播完就绪,动画会被截断;若 UI 晚于动画,则出现黑屏等待。这解释了为什么官方把默认动画压到约 4 秒(§0 实测)。 - **可复用于本任务的点**: - DSH 的启动动画**必须与主 UI 渲染解耦**:先出一个不透明底色的窗口 + 动画层,再让 React 首屏在背后完成挂载。这正是 C2 里 Electron 官方「backgroundColor + 立即 show」的建议所对应的做法。 - 动画结束与 UI 出现之间**显式设计一个过渡**(例如 150–250ms 的淡出),而不依赖「谁先就绪谁先上」的运气 —— 这是 DSH 相比 SteamOS 可以做得更好的地方。 ### A7. IT之家:官方开放自定义开机动画(中文二手源) - **URL**:https://m.ithome.com/html/680280.htm (实读,1158 字符) - **关键原文摘录**:`V 社已经创作了 20 个不同的开机动画供大家挑选……并在"设置">"自定义"中设置自己的开机动画,如果此前已经自定义过动画了(位于 steamui / overrides / movies/ 下),动画也会在新的 UI 中保留。`「如果是选择困难用户,V 社还添加了一个随机播放选项」 - **可复用于本任务的点**:官方把开机动画做成了**可选集 + 随机播放**的产品化能力,同时向后兼容旧目录(`steamui/overrides/movies/`)。DSH 若只做一个动画,也应把「选择/随机」的位置留在配置层,而不是硬编码。 ### A8. vovamod/Plymouth-SteamDeck(SteamOS 启动早期画面的一段) - **URL**:https://github.com/vovamod/Plymouth-SteamDeck (实读 raw README,1311 字符) - **关键原文摘录**:`A Plymouth theme featuring Steam Deck boot animation.` / `sudo update-alternatives --install /usr/share/plymouth/themes/default.plymouth default.plymouth /usr/share/plymouth/themes/steamdeck/steamdeck.plymouth 100` / `Original artwork/animations belongs to: Valve Corporation` - **可复用于本任务的点**:SteamOS 的开机视觉是**两段式**的 —— 内核/早期用户空间的 Plymouth 主题(静态或极简动画),之后才是 Steam 客户端的启动视频。DSH 同样可以两段式:**窗口底色/静态首帧(立即可见)→ 自绘启动动画**,第一段负责消灭白屏,第二段负责表现力。 ### A9. 特别查明的三项结论(汇总) **① Steam Deck 官方开机动画的实际画面内容与时长** - 时长(实测):`deck_startup.webm` = **4.008s**;`oled_startup.webm` = **4.000s**;`startup_machine.webm` = 3.117s;`bigpicture_startup.webm` = 6.000s(大屏模式启动时播放,见 A7 的「桌面模式下大屏幕模式启动时播放」)。 - 容器事实:VP9、450×450(两个开机动画)。文件名固定为 `deck_startup.webm`,位于 `Steam\steamui\movies\`。 - **画面内容:未取得可引用的一手来源。** 本机不具备抽帧能力(§0),Steam 官方页 / YouTube / Reddit 在本机网络下抓取失败(§D)。**故本文不对官方画面构图(黑底/居中 logo/几何线条等)作任何断言** —— 需要时请直接查看本机文件。 **② SteamOS 3 里「动画结束 → 桌面出现」的过渡方式** - 事实层面:音画分离、动画层与 UI 层并行初始化(A6 两个 issue 的原文)。 - 推断层面:**硬切,而非淡出**;黑屏等待来自「动画播完但 UI 尚未合成输出」的窗口期。 - 对 DSH 的行动项:显式加一个短淡出(150–250ms),并把底色与动画首帧统一,避免出现「黑屏 — 突然出 UI」的断裂。 **③ 自定义开机动画的格式与时长限制(反推官方工程约束)** - 格式:`.webm`(A2、A3),编码 VP8/VP9(Steam 社区讨论摘要中出现「must be in `.webm` format with VP8 or VP9 codec」,该条为搜索摘要,未取得页面原文,仅作旁证)。 - 比例:**1280×800、16:10**(A3 实读原文),与 Steam Deck 屏幕一致。 - 文件位置:官方 `steamui/movies/`(A2、A3);用户覆盖 `~/.steam/root/config/uioverrides/movies`(A4、A5)。 - 文件长度约束:**必须与原文件等长**(`truncate -s 1840847`,A2/A3 实读;且 1840847 与 §0 实测字节数完全一致)。这是「等长占位」的工程约束,不是视频本身的限制。 - **最大秒数:未找到任何官方或可引用的硬性上限。** 检索到的所有证据都指向「没有硬上限,但会被 UI 就绪时刻打断」(A6)。请勿采信「最长为 X 秒」的说法 —— 本次检索未找到支撑。 - 反推结论:官方真正在意的约束是 **① 比例匹配 ② 文件可等长覆盖 ③ 播放独立于 UI**,而不是时长上限。 --- ## B. 现成的 AI 提示词 / Skill / 增强方案 每条均标注类型与**可直接抄用的数值**。 ### B1. LottieFiles/motion-design-skill - **URL**:https://github.com/LottieFiles/motion-design-skill - **实读来源**:https://raw.githubusercontent.com/LottieFiles/motion-design-skill/main/skills/motion-design/SKILL.md (12489 字符) - **类型**:Agent Skill(SKILL.md,哲学优先、实现无关,声明兼容 CSS / Framer Motion / GSAP / Lottie) - **适用场景**:为「该不该动、动多久、用什么曲线、怎么编排」提供决策表 - **可直接抄用的具体数值**: - 动效性格原型:`Playful 150–300ms / ease-out-back / 过冲 10–20%`;`Premium 350–600ms / cubic-bezier(0.4,0,0.2,1) / 过冲 0%`;`Corporate 200–400ms / cubic-bezier(0.2,0,0,1) / 过冲 0–3%`;`Energetic 100–250ms / ease-out-expo / 过冲 15–30%` - 时长表:Tooltip/微反馈 80–120ms;Button press 120–180ms;Icon 150–250ms;Card 入场/退场 200–350ms;Modal 300–400ms;Page transition 400–600ms;**Dramatic reveal 600–1200ms** - 距离缩放:**100px 为基准;200px = 1.3×;400px = 1.6×** - 方向规则:**入场用 ease-out 家族(快起缓停);出场用 ease-in 家族;屏内移动用 ease-in-out** - **入场比出场长 30–50%**("Entrances 30-50% longer than exits") - Stagger 预算:Micro cascade 20–40ms(总 <200ms);Standard 50–100ms(总 <400ms);Dramatic 100–200ms(总 <600ms);Wave 30–60ms(总 <500ms);**总 stagger 必须 < 500ms** - 1/3 规则:**任何运动在无中间关键帧的情况下不得跨越超过屏幕的 1/3** - 重量分级:Heavy(Modal/Overlay)300–500ms、过冲 0%;Medium(Card/Panel)200–350ms、过冲 3–5%;Light(Tooltip/Badge)80–200ms、过冲 5–15% - 按钮按压配方:anticipation scale 0.97(50ms, ease-out)→ squash [1.04, 0.96](100ms, ease-in)→ follow-through 过冲到 1.02 回 1.0(spring 200ms),总计约 150ms 按压 + 200ms 回稳 - **可复用于本任务**:启动动画落在 **Dramatic reveal 600–1200ms** 区间(配合 1/3 规则),面板「由外向内」入场落在 **Standard stagger 50–100ms / 总 <400ms**,曲线一律 ease-out 家族。这两条把两个动画的尺度一次性钉死。 ### B2. mblode/agent-skills · ui-animation - **URL**:https://github.com/mblode/agent-skills - **实读来源**:https://raw.githubusercontent.com/mblode/agent-skills/main/skills/ui-animation/SKILL.md (23598 字符) - **类型**:Agent Skill(含参考文件拆分:spring-animations.md / contextual-animations.md / choreography.md / component-patterns.md) - **适用场景**:UI 动作的实现与评审,含「从录屏反推缓动曲线」的工作流 - **可直接抄用的具体数值**: - 命名曲线:**Enter `cubic-bezier(0.22, 1, 0.36, 1)`**;**Move `cubic-bezier(0.25, 1, 0.5, 1)`**;**Drawer `cubic-bezier(0.32, 0.72, 0, 1)`**;Expo out `cubic-bezier(0.19, 1, 0.22, 1)`;Press `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;On-screen move `cubic-bezier(0.645, 0.045, 0.355, 1)` - 时长表:Button press 100–160ms;Tooltip/小浮层 125–200ms;Dropdown 150–250ms;Modal/Drawer 200–350ms;屏内滑动 200–300ms;Page transition 250–400ms;Hover 变色 200ms;Hover 缩放 100–150ms - **实现优先级:CSS transition > WAAPI > CSS keyframes > JS(`requestAnimationFrame`)**;理由原文:`under load CSS stays smooth while JS drops frames` - **禁止动画布局属性**:`Never animate layout properties (width, height, top, left); they trigger layout recalc every frame.` - 交错:**30–50ms stagger,总时长 <300ms**,最重要的元素领先 - 弹层起始缩放:**`scale(0.9–0.96)`,不要用 `scale(0)`**(小浮层取低值、整屏对话框取高值) - 配对元素(弹窗+遮罩、tooltip+箭头)**必须共用同一缓动与时长** - **可复用于本任务**:**入口曲线 `cubic-bezier(0.22,1,0.36,1)` + stagger 30–50ms** 与 DSH 零依赖约束完全对齐(全部是 CSS)。「实现优先级」和「禁动布局属性」两条可直接写进实现规范。 ### B3. dylantarre/animation-principles(Disney 12 原则) - **URL**:https://github.com/dylantarre/animation-principles - **实读来源**:https://raw.githubusercontent.com/dylantarre/animation-principles/main/README.md (首次抓取失败,重试成功)+ `api.github.com/repos/dylantarre/animation-principles/contents/` 与 `.../contents/skills` - **类型**:Agent Skill 市场(144 个技能,12 大类 × 12 个) - **结构实读**:`skills/01-by-domain` … `skills/12-by-problem-type`;其中 `skills/11-by-principle-focus/` 下是 12 个 `*-mastery` 目录(`squash-stretch-mastery`、`anticipation-mastery`、`staging-mastery`、`straight-ahead-pose-mastery`、`follow-through-overlapping`、`slow-in-out-mastery`、`arc-mastery`、`secondary-action-mastery`、`timing-principle-mastery`、`exaggeration-mastery`、`solid-drawing-mastery`、`appeal-mastery`) - **12 原则原文**(README 表格):Squash & Stretch / Anticipation / Staging / Straight Ahead / Pose to Pose / Follow Through / Overlapping / Slow In / Slow Out / Arc / Secondary Action / Timing / Exaggeration / Solid Drawing / Appeal - **可直接抄用的数值**:该 README 与目录**未给出毫秒/贝塞尔数值**(如实说明),它是原则层而非参数层。 - **可复用于本任务**:给动画评审一张检查表 —— **Anticipation** → 启动动画的预备帧(先收后放);**Staging** → 面板入场时一次只让一个面板动,其余保持静止,避免注意力被切碎;**Slow In/Slow Out** → 直接决定我们选 ease-out(对应 B1/B2 的曲线);**Follow Through / Overlapping** → 面板停下后内容再淡入(延迟 50–100ms)。 ### B4. freshtechbro/claudedesignskills · `.claude/skills/motion-framer/SKILL.md` - **URL**:https://github.com/freshtechbro/claudedesignskills - **实读来源**:https://raw.githubusercontent.com/freshtechbro/claudedesignskills/main/.claude/skills/motion-framer/SKILL.md (21004 字符) - **类型**:Agent Skill(面向 Motion / Framer Motion 库) - **适用场景**:React 组件的微交互、页面过渡、布局动画、手势 - **可直接抄用的具体数值**: - **`staggerChildren: 0.1`**(子元素交错 100ms)—— 容器 variants 传播给子项的标准写法 - variants 结构:`hidden: { opacity: 0, y: 20 }` → `visible: { opacity: 1, y: 0 }` → `exit: { opacity: 0, scale: 0.9 }` - 单元素入场:`initial={{ opacity: 0, y: 50 }}` → `animate={{ opacity: 1, y: 0 }}` - Spring:`{ type: "spring", stiffness: 300, damping: 20 }` - 手势:`whileHover={{ scale: 1.05/1.1 }}`、`whileTap={{ scale: 0.9/0.95 }}`;手势开始 `duration: 0.1`、手势结束 `duration: 0.5` - 属性级拆分过渡:`transition={{ x: { type: "spring", stiffness: 300 }, opacity: { duration: 0.2 } }}` - **可复用于本任务**:**只抄结构与数值,不抄库**。该技能依赖 Framer Motion(运行时依赖),与 DSH 约束冲突 —— 但「容器 variants 控制子项 + staggerChildren 0.1」这一编排模型可以直接用 **CSS 自定义属性** 实现:父级加状态属性,子项用 `transition-delay: calc(var(--i) * 100ms)`。 ### B5. ui-skills.com/skills/motion - **URL**:https://www.ui-skills.com/skills/motion - **状态**:**实访问(HTTP 200,剥离后约 57715 字符),但正文未取得** —— 该页是 Astro + 客户端渲染,HTML 中可读到的只是技能索引(`astro-island` 的 props),SKILL.md 正文需 JS 渲染。 - **读到的内容(索引清单,节选)**:motion 分类下挂着 `emilkowalski/animate`、`emilkowalski/animate-expo`、`emilkowalski/apple-design`、`emilkowalski/review-animations`、`emilkowalski/improve-animations`、`emilkowalski/find-animation-opportunities`、`emilkowalski/animation-vocabulary`、`iart-ai/60fps-animation`、`iart-ai/accessible-animation`、`greensock/gsap-*` 系列等。索引中对 `animate` 的原文描述:`Build an animation from scratch, making the decisions in the order that determines whether it feels right — should it animate at all, what purpose, which tool, which properties, which curve and duration, how it interrupts, how it exits.` - **类型**:设计原则库 / 技能索引站 - **可复用于本任务**:**当作选型路由表用,而不是数值来源**。它的价值在于把「动效决策」拆成了可检索的技能族(生成 / 评审 / 审计 / 无障碍),并指向本任务的 §B6 上游仓库。需要数值时走 B6。 ### B6.(自找 ①)emilkowalski/skills —— 本次检索中最适合直接抄的一套数值 - **URL**:https://github.com/emilkowalski/skills (★42879) - **实读来源**: - https://raw.githubusercontent.com/emilkowalski/skills/main/README.md - https://raw.githubusercontent.com/emilkowalski/skills/main/skills/animate/SKILL.md (11779 字符) - https://raw.githubusercontent.com/emilkowalski/skills/main/performance-cheatsheet.md - **类型**:Agent Skill(作者自述基于其在 Vercel / Linear 的经验;含 animate / review-animations / improve-animations / emil-design-eng 等) - **适用场景**:从零构建一个动画并决定曲线、时长、属性、中断与退场 - **可直接抄用的具体数值**: - CSS 令牌(原文即以 CSS 变量给出): - `--ease-out: cubic-bezier(0.23, 1, 0.32, 1)` —— 标注为 "strong ease-out for UI" - `--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1)` —— "strong ease-in-out for on-screen movement" - `--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1)` —— "iOS-like drawer curve (Ionic)" - 时长表:Button press 100–160ms;Tooltip / 小浮层 125–200ms;Dropdown / Select 150–250ms;Modal / Drawer 200–500ms - **`UI animations stay under 300ms`**;原文对比:「a 180ms dropdown feels more responsive than a 400ms one」 - 方向规则:进入/退出 `ease-out`;屏内移动/形变 `ease-in-out`;默认 `ease-out`;**"Never `ease-in` on UI"** - 交错:**"Everything entering at once" → `30–80ms stagger`** - 弹簧:`{ type: "spring", duration: 0.5, bounce: 0.2 }`(Apple 风格,易推理)或 `{ type: "spring", mass: 1, stiffness: 100, damping: 10 }`(传统物理) - 硬规则:**"No approximated values."** 每个曲线/时长/弹簧参数都必须来自表,禁止凭手感编一个 `cubic-bezier(0.4, 0, 0.2, 1)` - 反模式表:`ease-in on a UI element` → 改成 `ease-out` 或强自定义曲线;**`UI duration over 300ms with no reason` → 改成 150–250ms**;`Built-in ease-out` → 改成 `cubic-bezier(0.23, 1, 0.32, 1)` - 实现规则:`CSS animations beat JS under load — they run off the main thread`;"Transitions, not keyframes, for anything triggered rapidly"(transition 会从当前值重定向,keyframes 会从零重启) - **可复用于本任务**:**三条曲线 + 三档时长 + 30–80ms stagger 可以直接落成 DSH 的动画令牌**,与「不引入依赖」的约束天然兼容(纯 CSS 变量)。「Never ease-in on UI」直接约束面板入场。 ### B7.(自找 ②)joepUI/motion-ref-skill —— 与「由外向内平移入场」最贴的一篇 - **URL**:https://github.com/joepUI/motion-ref-skill (★6) - **实读来源**: - https://raw.githubusercontent.com/joepUI/motion-ref-skill/main/SKILL.md (2177 字符,中文) - https://raw.githubusercontent.com/joepUI/motion-ref-skill/main/ref/13-enter-exit.md (入场退场分类参考) - **类型**:Agent Skill(中文动效参考库,按场景路由到 13 个分类文件) - **可直接抄用的具体数值**(均为原文): - 核心原则:`常规交互控制在 200-500ms;超过 500ms 容易显得卡,低于 100ms 用户难感知`;`代码方案必须包含 prefers-reduced-motion: reduce 适配` - **13.5 Stagger 序列入场**:`每项延迟 50-100ms,单项动画 300ms`;`animation-delay: calc(var(--i) * 60ms)`;`总时长不超过 800ms。6 个以上元素时缩小延迟间隔` - **13.7 Slide In 横向滑入**:`250-400ms,ease-out`;`transform: translateX(-100%) → translateX(0)`;`全屏滑入用 100%,局部元素用 20-40px` - 13.2 Slide Up 上滑入场:`从下方 20-30px 处滑入 + 淡入,300-500ms,ease-out`;`位移量不要太大,20-30px 足够` - 13.3 Scale In:`0.9 → 1 + 淡入,200-350ms`;`起始值 0.9-0.95,不要从 0 开始` - 13.4 Spring In:`400-600ms`,`cubic-bezier(0.34, 1.56, 0.64, 1)`(第二、四参数 >1 产生过冲) - 13.1 Fade In:`200-400ms`,`opacity: 0 → 1; transition: opacity 300ms ease`;标注为 `prefers-reduced-motion 的最佳降级方案` - 13.6 Blur In:`400-600ms`,`filter: blur(8px) → blur(0)`;注明「filter 动画比 transform 性能差,少量元素使用」 - **可复用于本任务**:**这就是「面板由外向内平移入场」的现成配方** —— 13.7(横向滑入 250–400ms ease-out、局部元素位移 20–40px)+ 13.5(`calc(var(--i) * 60ms)` 交错、总 <800ms)+ 13.1 作为 reduced-motion 降级。CSS 变量写法可直接复制。 ### B8.(自找 ③)AThevon/genjutsu - **URL**:https://github.com/AThevon/genjutsu (★393) - **实读来源**:https://raw.githubusercontent.com/AThevon/genjutsu/main/README.md (44260 字符) - **类型**:Agent Skill 插件(Claude Code / claude.ai / Cowork) - **实读要点**:三层结构 `cast`(让一个界面动起来)/ `paint`(给产品视觉身份)/ `bunshin`(一个艺术总监 + 多个克隆代理构建整站);覆盖 Web(React / Vue / Svelte / Astro / 原生 CSS / Three.js / Canvas)、Android、Apple - **可直接抄用的具体数值**:README 未给出毫秒/曲线数值(如实说明) - **可复用于本任务**:它的**决策顺序**(先问「该不该动」→ 再问「目的」→ 再定工具/属性/曲线)可以作为 DSH 动画评审流程的骨架;数值需从 B6/B7 取。 **B 部分覆盖度**:任务指定的 5 条(B1–B5)全部实际抓取;自找 3 条(B6–B8),其中 B6 是数值密度最高的一条。 --- ## C. 技术实现参考 ### C1. SVG 线条描画:让「线长成图形」 **来源 1**:Jake Archibald《Animated line drawing in SVG》 - **URL**:https://jakearchibald.com/2013/animated-line-drawing-svg/ (实读,剥离后 5390 字符) - **关键原文摘录**: - `stroke-dasharray lets you specify the length of the rendered part of the line, then the length of the gap. stroke-dashoffset lets you change where the dasharray starts.` - `Drag both sliders up to their maximum, then slowly decrease the dashoffset. Voilà, you just made the line draw!` - 取长度:`var path = document.querySelector('.squiggle-container path'); path.getTotalLength(); // 988.0062255859375` - `Avoid using SMIL, it also isn't supported in IE and doesn't perform well in Chrome & Safari` - 用 CSS transition/transition 实现;先 `transition = 'none'` 清旧过渡,再设 `strokeDasharray = length + ' ' + length`、`strokeDashoffset = length`,然后触发一次布局再开始动画 - **可复用于本任务**:启动动画的核心手法。**要点是「先用同一长度同时设置 dasharray 与 dashoffset(制造不可见状态),再触发一次重排,最后把 dashoffset 归零」** —— 少了中间那次强制布局,动画会静默失效(这是最常见的坑)。 **来源 2**:CSS-Tricks《How SVG Line Animation Works》 - **URL**:https://css-tricks.com/svg-line-animation-works/ (实读,剥离后 9322 字符) - **关键原文摘录**: - `.path { stroke-dasharray: 1000; stroke-dashoffset: 1000; animation: dash 5s linear forwards; } @keyframes dash { to { stroke-dashoffset: 0; } }` - `If doing it with CSS, you'll want the animation to have animation-fill-mode of forwards so the final state remains how the animation ends.` - `You just need to make stroke-dasharray a longer value than the length of the stroke.` - `Most of the examples you see of SVG line animations use JavaScript. That's because it's hard to know what the length of that stroke actually is.` - 评论区 Antonio 的实测反馈:`I could not get the total length from non-paths. So I made a little thing to convert all the non-paths into paths first` - **踩坑清单(两条来源合并)**: 1. **`getTotalLength()` 只对 `` 有效**,``/``/`` 取不到 → 先用路径工具把图形转成 path(对 DSH:SVG 一律用 `` 绘制 logo 与线条)。 2. **必须带 `forwards`(或 `animation-fill-mode: forwards`)**,否则动画结束会弹回不可见状态。 3. **`stroke-dasharray` 要 ≥ 实际路径长度**,否则线条会分成两段。 4. 长度值可以硬编码(省掉 JS),但换 viewBox 缩放后要重新核对 —— 用相对单位或运行时计算更稳。 5. **不要用 SMIL**(``):性能与兼容性都更差,用 CSS transition / animation。 ### C2. Electron 启动白屏:成因与规避 **来源 1**:Christian Engvall《Electron white screen app startup》 - **URL**:https://www.christianengvall.se/electron-white-screen-app-startup/ (实读,剥离后 2884 字符) - **关键原文摘录**: - 成因:`If you start the electron tutorial app you can see it flashes white and then loads the css setting the correct color.` - 方案 A(backgroundColor):`new BrowserWindow({ titleBarStyle: 'hidden', width: 1281, height: 800, backgroundColor: '#312450' })` - 方案 B(ready-to-show):`show: false` + `mainWindow.once('ready-to-show', () => { mainWindow.show() })` - 引官方原文:`Note that even for apps that use ready-to-show event, it is still recommended to set backgroundColor to make app feel more native.` - **可复用于本任务**:**启动窗口的底色必须设成与启动动画底色一致的颜色**(对 SteamOS 风格即深黑),这是消灭白屏的第一步,且无论后续是否使用 ready-to-show 都要设。 **来源 2**:Electron 官方 BrowserWindow 文档 - **URL**:https://www.electronjs.org/docs/latest/api/browser-window (实读,剥离后 24879 字符) - **关键原文摘录**(真实读到的三句): - `While loading the page, the ready-to-show event will be emitted when the renderer process has rendered the page for the first time if the window has not been shown yet. Showing the window after this event will have no visual flash` - `Please note that using this event implies that the renderer will be considered "visible" and paint even though show is false. This event will never fire if you use paintWhenInitiallyHidden: false` - `For a complex app, the ready-to-show event could be emitted too late, making the app feel slow. In this case, it is recommended to show the window immediately, and use a backgroundColor close to your app's background` - **可复用于本任务**(关键判断):**DSH 有启动动画,属于「复杂应用」,不应等 `ready-to-show` 才显示窗口** —— 否则动画要等到渲染完成才开始,用户先看到的是一段时间的空白。正确做法是:**立即 show + 深色 backgroundColor + 在页面内先渲染启动动画层**,让 React 首屏在动画背后完成挂载,动画结束再交接(呼应 A6 的时序教训)。 ### C3. 批量元素 stagger 入场且不触发 layout thrash **来源 1**:CSS-Tricks《Staggered CSS Transitions》 - **URL**:https://css-tricks.com/staggered-css-transitions/ (实读,剥离后 10171 字符) - **关键原文摘录**: - 逐项延迟:`.list li:nth-child(1) span { transition-delay: 0.0s; } … :nth-child(6) { transition-delay: 0.25s; }`(0.05s 步长) - 用变量驱动:`.list { --delay: 0.05s; }` + `transition-delay: calc(var(--delay) * 3)` - 局限:`Say your list starts to grow, perhaps to seven or more items. The staggering suddenly isn't working on the new ones because this doesn't account for that many list items.` - **可复用于本任务**:**这是零依赖错峰入场的标准手法** —— 但避开 `nth-child` 的硬编码上限,改为在渲染时给每个面板写 `style="--i: 3"`,CSS 里统一 `transition-delay: calc(var(--i) * 60ms)`(数值取自 B7 的 13.5)。 **来源 2**:MDN《Animation performance and frame rate》 - **URL**:https://developer.mozilla.org/en-US/docs/Web/Performance/Animation_performance_and_frame_rate (实读) - **关键原文摘录**: - `For a rate of 60 frames per second, the browser has 16.7 milliseconds to execute scripts, recalculate styles and layout if needed, and repaint the area being updated.` - 渲染瀑布:`Recalculate Style → Layout → Paint → Composition` - 高代价属性(触发 style + layout + repaint):`left, max-width, border-width, margin-left, font-size` - 走合成、连 repaint 都不触发的属性:`transform, opacity` - **可复用于本任务**:**面板入场只动 `transform` 与 `opacity`** —— 这既是性能要求,也正好是「由外向内平移」的实现方式(`transform: translate3d()` 而非 `left/top`)。 **来源 3**:emilkowalski/skills · performance-cheatsheet.md + animate/SKILL.md(同一套来源,见 B6) - **关键原文摘录**:`Motion's x/y drops frames → Animate the full transform string instead`;`CSS animations beat JS under load — they run off the main thread, while requestAnimationFrame-based animation drops frames while the browser loads, scripts, or paints.`;`Use CSS for predetermined motion, JS for dynamic and interruptible motion.` - **可复用于本任务**: - **启动动画是「预定序列」→ 用纯 CSS animation/keyframes**;面板入场是「可被打断的交互」→ 优先 CSS transition(transition 会从当前值重定向,keyframes 会从零重启)。 - **批量入场不要用 JS 逐帧驱动**,也不要只用 `x/y` 简写这类非合成路径;写成完整 `transform: translate3d(...)` 字符串。 - 不要和昂贵工作在同一个 tick 里启动动画:先启动动画,让一帧落地,再做重活(对 DSH:启动动画层先动,首屏数据加载延后一拍)。 **未取得的来源**:`https://web.dev/articles/animations-guide` 抓取失败(§D),已用 MDN 的同类内容替代。 --- ## D. 检索失败清单(如实记录) | 目标 URL | 现象 | 判断 | 替代来源 | | --- | --- | --- | --- | | https://steamcommunity.com/games/1675200/announcements/detail/3301726014486703381?l=schinese | `fetch failed`(重试仍失败) | 站点在本机网络下不可达 | A7(IT之家对该公告的中文转述,实读) | | https://store.steampowered.com/points/shop/app/1675200/ | `fetch failed` | 同上 | — | | https://store.steampowered.com/news/app/1675200/view/3301726014486703381 | `fetch failed` | 同上 | A7 | | https://help.steampowered.com/zh-cn/faqs/view/69E3-14AF-9764-4C28 | HTTP 200,但正文为 JS 渲染,仅返回导航壳(909 字符) | 客户端渲染 | A6 的 Valve issue(一手行为证据) | | https://www.xda-developers.com/how-to-change-boot-up-animation-steam-deck/ | `fetch failed` | 站点拦截/不可达 | A2、A3 | | https://www.androidpolice.com/change-steam-deck-boot-up-animation-tutorial/ | `fetch failed` | 同上 | A2、A3 | | https://www.gamingonlinux.com/…(官方自定义动画报道) | 未找到该篇具体地址;站内其它文章可读 | 检索未命中 | A7 | | https://steamdeckhq.com/news/steam-deck-client-stable-and-steamos-3-3-2-update | 抓取返回异常(后续重试未成功) | 网络抖动 | A6 | | https://decky.net/en/blogs/news-en/custom-boot-video-steam-deck | HTTP 200,但正文是 JS 重定向脚本(5790 字符全是 `render`/`redirect` 逻辑),未取到正文 | 客户端渲染/脚本注入 | A3(同类的 1200×800 说法未获原文,故未采用) | | https://steamdeckrepo.com/ | HTTP 200,剥离后仅剩站点标题(15 字符) | 客户端渲染 | A4(插件的动画源站指向,可读) | | Reddit(web_fetch 与 platform_search 均失败) | `fetch failed` | 不可达 | A6(Valve issue 的同类证据) | | https://web.dev/articles/animations-guide | `fetch failed` | 不可达 | C3 来源 2(MDN) | | https://raw.githubusercontent.com/SteamDeckHomebrew/decky-animation-changer/main/README.md | 404 | 仓库路径不存在(实际仓库为 TheLogicMaster/SDH-AnimationChanger) | A4 | | https://raw.githubusercontent.com/dylantarre/animation-principles/main/README.md | 首次 `fetch failed`,重试成功 | 网络抖动 | —(已成功) | | https://github.com/**(所有网页版页面) | HTTP 200 但只返回导航壳 | GitHub 已改为客户端渲染 | 全部改抓 raw.githubusercontent.com / api.github.com | | https://www.ui-skills.com/skills/motion 与其技能详情页 | HTTP 200,但正文为客户端渲染 | Astro 岛 + JS | B6(上游 GitHub 仓库原文) | **未能找到的类型(如实说明)**: - **Steam Deck 官方开机动画的逐帧画面描述**(可引用一手来源):未找到。仅有容器级实测数据(§0)。 - **自定义开机动画的最大秒数硬上限**:未找到任何官方或可引用的规定。检索到的证据都指向「无硬上限,但会被 UI 就绪时刻打断」。 - **「SteamOS 3 动画到桌面」的官方流程文档**:未找到;只能通过 Valve issue 的用户行为描述反推(A6,已标注推断)。 - **Reddit 上的入场动画实践帖**:本机网络不可达,未取得。 --- ## E. 最值得直接复用的 3 条 **① emilkowalski/skills 的曲线与时长令牌(B6)** 理由:它是本次检索中**数值密度最高、且与零依赖约束完美兼容**的一条 —— 三组曲线直接以 CSS 变量形式给出(`--ease-out: cubic-bezier(0.23,1,0.32,1)`、`--ease-in-out: cubic-bezier(0.77,0,0.175,1)`、`--ease-drawer: cubic-bezier(0.32,0.72,0,1)`),外加「UI 动画 <300ms」「禁止 ease-in」「30–80ms stagger」「禁止凭手感编曲线」四条硬规则。可以直接落成 DSH 的 `animation-tokens.css`,两套动画共用。 **② joepUI/motion-ref-skill 的 13.5 + 13.7 组合(B7)** 理由:任务里的「由外向内平移入场」几乎就是它的原样配方 —— **横向滑入 250–400ms / ease-out / 局部元素位移 20–40px**(13.7)+ **`animation-delay: calc(var(--i) * 60ms)`、单项 300ms、总时长 <800ms、超过 6 个元素就缩小间隔**(13.5),且明确给出 `prefers-reduced-motion` 的降级(13.1 淡入 200–400ms)。中文表述、CSS 变量写法,迁移成本最低。 **③ 本机实测的官方启动动画基准 + Electron 官方白屏结论(§0 + C2)** 理由:这一条同时解决了「做多长」和「怎么不白屏」两个决策 —— 官方开机动画实测为 **4.008s / 4.000s(两代一致)**,说明 4 秒是经过验证的产品节奏(对应 B1 的 Dramatic reveal 区间);而 Electron 官方文档明确说「复杂应用不要等 `ready-to-show`,立即 show 并设置接近应用背景的 `backgroundColor`」。两者结合,DSH 的启动动画应当**立即显示一个深色窗口、在页面内自绘动画、与 React 首屏并行、结束前显式淡出交接**(A6 的 SteamOS 教训正是反面教材:音画先行、UI 后到,导致黑屏)。 --- ## 附:可直接落地的数值速查(来源标注) | 用途 | 数值 | 来源 | | --- | --- | --- | | 启动动画总时长 | 3.5–4.5s(官方基准 4.008s / 4.000s) | §0 实测、B1 Dramatic reveal 600–1200ms 之上按 1/3 规则放大 | | 动画结束→UI 交接 | 显式淡出 150–250ms | A6 推断 + B6 时长表 | | 面板入场时长 | 250–400ms | B7 13.7、B2 屏内滑动 200–300ms | | 面板入场曲线 | `cubic-bezier(0.23, 1, 0.32, 1)`(强 ease-out) | B6 | | 备选入场曲线 | `cubic-bezier(0.22, 1, 0.36, 1)` | B2 | | 面板位移 | 局部元素 20–40px;全屏滑入 100% | B7 13.7、13.2(20–30px) | | 交错间隔 | `calc(var(--i) * 60ms)`(30–80ms 区间) | B7 13.5、B6 | | 交错总时长 | <800ms(B7 13.5);更严口径:Standard <400ms 且总 stagger <500ms(B1)、<300ms(B2) | B7、B1、B2 | | 属性限制 | 只动 `transform` + `opacity` | C3(MDN)、B2 | | 退场曲线 | `ease-in` 家族,且比入场短 30–50% | B1 | | 缩放起始 | 0.9–0.95,禁止 `scale(0)` | B2、B7 13.3 | | 无障碍降级 | `@media (prefers-reduced-motion: reduce)` → 纯淡入 200–400ms | B7 13.1 | | 窗口底色 | 与动画底色一致的深色(如 `#000`/`#0e0e10`) | C2(Electron 官方) | | 窗口显示时机 | 立即 show,不等 `ready-to-show` | C2(Electron 官方,复杂应用条款) |