diagram-design 自动播放治理深度解析:为什么reveal是唯一被认可的 autoplay 模式(ADR 0003)
【免费下载链接】diagram-design38 editorial diagram types for Claude Code, Codex, and Pi. Self-contained HTML + SVG. No shadows. No Mermaid slop.项目地址: https://gitcode.com/GitHub_Trending/di/diagram-design
本文以仓库 ADR 0003 — reveal is the only sanctioned autoplay 为骨架,结合 动画契约、固定控制器模板 与 verify-motion.py 源码,讲清 diagram-design 项目中"加载即自动播放"的唯一合法形态、其背后的架构决策与验证机制,以及如何在生成动画图表时正确使用
reveal模式。读完你可以:精确区分none / reveal / step / loop四种模式的启动权边界,理解为什么reveal只允许"一次性"运行、而任何重复播放都是反模式,并能用仓库自带的验证器守住这条红线。
一、背景:motion 契约中的一处"自相矛盾"
diagram-design 生成的图表是自包含的单 HTML + SVG 文件,默认完全静态(data-motion-mode="none")。当用户明确要求动效时,动画只用来"解释一张完整的静态图",绝不补充缺失的含义(见 animation.md)。
问题出在早期版本:motion 契约把"加载即自动播放(autoplay on load)"列为反模式,但官方控制器(canonical controller)却在页面加载时启动了一轮reveal运行。两份陈述没有彼此调和,任何照着模板复制图表的用户都会读到一处"矛盾"——这正是 ADR 0003 要裁决的问题。
ADR 0003 的结论不是废除其中一方,而是精确划界:autoplay 反模式指的不是"交互前有任何运动",而是**"重复的、或用于吸引注意力的自动播放"**。在此定义下,reveal的一次性加载播放不但合规,而且是唯一被认可的 autoplay。
二、决策:reveal的完整行为契约
ADR 0003 给出了严格的行为定义,可拆解为四条:
- 一次性:
reveal模式允许在初始加载时运行一次。它服务于"简短的有序解说"——此时强制用户先点播放再观看反而是摩擦。 - 终态完整:运行结束后停留在完整终态(
data-frame="end"),永不反复。 - 永不自发重启:视口重新进入(viewport re-entry)、标签页切回(tab return)都不会触发重播;重播只能来自用户的显式 Replay 操作(按钮或
R键)。 - 降级完整:在
prefers-reduced-motion: reduce下,或完全没有 JavaScript 时,所有模式一律呈现完整静态帧。
四种模式谁"能动"
结合 animation.md 的模式表,四种模式的启动权边界一目了然:
| 模式 | 行为 | 控制 / 实现 | 适用场景 |
|---|---|---|---|
none | 完整稳定图形 | 无 JavaScript | 默认;打印、截图、导出、reduced-motion 降级(无播放控件) |
reveal | 一次确定性自动播放,结束于完整态 | ≤5s 可纯 CSS;否则使用固定控制器 | 简短有序解说;绝不自动重播 |
step | 暂停在语义状态上 | 最小内联 JS 绑定 Play/Pause/Replay/Previous/Next | 教学、对比、策略追踪 |
loop | 一个装饰性 token 重复,不改变语义 | 仅 CSS | 安静的流转提示;周期 ≥3s |
规则可以浓缩成一句话:只有loop会重复。队列状态、输入过程、字段取值、策略结论、包含关系和审计条目,一律用reveal或step,且结束于完整态。reveal是唯一被认可的 autoplay:它只在使用者明确请求动效时于初始加载运行一次,之后保持完整;视口重入或无显式 Replay 操作时绝不重启。
三、静态优先:reveal成立的前提契约
reveal之所以"只允许一次",是因为整条 motion 体系建立在static-first(静态优先)之上。动画契约的增强流程(见 animation.md 第 21-29 行)保证了:
- 源即完整:所有语义节点、标签、连接线、状态和结论在增强前就完整可见,只有
.motion-ready作用域内的选择器才允许隐藏/变换它们; - 稳定捕获:初始
data-frame="static"、?motion=static、打印、无 JS、独立 SVG 导出都暴露完整帧并隐藏控件与装饰 token; - 单一时钟:
--motion-fast: 160ms、--motion-step: 480ms、--motion-hold: 720ms,--motion-total不超过8000ms,延迟由整数步推导,无随机、弹簧或 transition-event 计时; - 失败即完整:JavaScript 只有在控件绑定成功、初始渲染成功后才添加
.motion-ready;在此之前任何脚本错误都让完整源可见。
因此reveal的一次性播放不会造成"空图等待":即使播放被打断,页面上始终存在完整的静态语义。这也解释了 reduced-motion 的降级为何如此干净——它只是回到动画还没开始的静态帧。
四、源码级证据:固定控制器里的 reveal 分支
ADR 0003 的另一半结论是:验证器不需要 autoplay 启发式,因为"固定控制器是唯一能启动一轮运行的代码,而它恰好实现了这条策略"。这句话的底层支撑来自 ADR 0001:需要动效的文件只能携带恰好一个<script>} else if (root.dataset.motionMode === 'reveal') { setControlsAvailable(true); render(0, false); play(false, false); }
位于 template-motion.html 第 419-422 行。这是启动时唯一会调用play(...)的分支:step模式停在Ready · step 0 of count,none与 reduced-motion 走静态分支,loop走 CSS 动画;只有reveal从第 0 步自动播放到终态。
2. 立即停止、绝不循环:
tick()在每步推进前检查if (step >= count) { finish(); return; }(第 305-311 行),finish()把data-frame置为end并清空计时器。整个播放是一条setTimeout链,没有setInterval,也就没有天然的重复来源。真正的重复只存在于loop模式下的 CSSanimation-iteration-count: infinite,且被[data-motion-mode="loop"]作用域严格限定(第 78-80 行)。
3. 视口重入与标签页切回不重启:
控制器只监听了一个visibilitychange:页面隐藏时暂停(第 353-355 行),切回可见时不会 resume、更不会重播。代码中不存在IntersectionObserver或任何 viewport 监听,因此"滚动回来又播一遍"在架构上就是不可能发生的。
4. reduced-motion 与静态降级:
初始化分支里,reduce.matches || root.dataset.motionMode === 'none'直接渲染完整静态帧、隐藏并禁用全部播放控件、把data-motion-state置为reduced/static,并输出状态文本(第 402-410 行)。?motion=static或<html>@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; } [data-motion-item] { opacity: 1 !important; transform: none !important; } [data-motion-decorative] { display: none !important; } [data-motion-controls] { display: none !important; } }
配合noscript提示(无 JS 时展示"完整终图已在上方显示"),就构成了"无 JS 也完整、减动效也完整"的双保险。
一个容易被误读的细节:控制器在用户运行时切换prefers-reduced-motion时,若切回普通模式且当前是reveal,会恢复播放(第 386-388 行)。这是响应用户主动改变系统偏好,不是视口重入或标签页切回,与 ADR 0003 不冲突——它同样要求初始运行未被消耗(resumeAfterReduce只在 reveal 且原本正在播放时为真)。
五、验证器如何"不需要 autoplay 启发式"
verify-motion.py 通过结构性检查 + 身份检查把策略变成"构造即成立",而不是靠运行时探测:
- 模式白名单:
MODES = {"none", "reveal", "step", "loop"}(第 16 行),任何拼写变体直接报错; - 脚本资格:
none/loop必须零脚本、零控件(第 356-359 行);reveal若携带脚本则自动进入受控检查,必须提供完整的 Play/Pause/Replay/Previous/Next 五键与data-motion-status活区(第 336-354 行); - 身份检查:脚本必须只带
data-diagram-controls属性,且归一化后与模板控制器逐字符相等(第 369-385 行)——手改过的控制器必然失败; - 无限动画只属于 loop:
infinite_unscoped_selectors扫描所有animation: ... infinite规则,不在[data-motion-mode="loop"]作用域内的一律拒绝(第 229-247 行)。这一条直接封死了"把 reveal 改成无限重复"的路径; - 失败安全:
motion-ready必须在初始渲染成功之后添加(第 445-453 行),保证任何提前增强都让静态源可见。
配套的对抗性测试 test-verify-motion.py 会逐项证明"违规必被拒":例如把一个loop图改成两个语义 item(loop-two-semantic-items)会被拒绝;在data-motion-item上直接opacity:0(破坏无 JS 回退)会被拒绝;把visibilitychange换成pagehide甚至用注释伪造也会被拒绝(第 159-199 行)。换句话说,"重复 autoplay"在进入浏览器之前就被构建管线拦下了——这正是 ADR 0003 不需要启发式探测的原因:唯一能启动播放的代码是固定控制器,而固定控制器只实现了"一次性 reveal"。
六、实操:如何合规地使用 reveal
在 animation.md 的验证命令 基础上,生成或检查一个带reveal的动画图表:
# 校验单个动画图表(模式声明、步数连续性、预算、控制器身份、可访问性等) python3 scripts/verify-motion.py path/to/animated-diagram.html # 运行对抗性测试套件,确认验证器本身的行为 python3 scripts/test-verify-motion.py # 皮肤 lint:SHA-256 身份检查 + 其他皮肤规则 python3 scripts/lint-skin.py path/to/animated-diagram.html编写时的合规要点:
- 在
[data-motion-root]上声明data-motion-mode="reveal"与data-step-count(1–8,推荐 3–6);语义步必须连续,每步最多两个 item,全部 item 不超过 12 个; - 播放预算内(总时长 3–8s,
--motion-total≤8000ms);≤5s 的简短解说可以纯 CSS 实现 reveal(此时无脚本、无控件,验证器同样放行); - 需要控制器时,直接复制 template-motion.html 的脚本体,只改内容与 slug 前缀 ID,不要新增任何
IntersectionObserver或可见性恢复逻辑; - 视觉回归只允许通过
?motion=step&step=N(N 为非负十进制整数,0 ≤ N ≤data-step-count)获取零时长的精确帧,捕获前等待document.fonts.ready并断言data-frame; - 语义项必须有非颜色的
aria-label,装饰项必须aria-hidden="true" focusable="false";SVG 的<title>/<desc>描述完整含义而非动画。
浏览器终检(animation.md 第 125-132 行):禁用 JS 后完整图仍可见可读;模拟prefers-reduced-motion: reduce后终态完整、控件隐藏禁用、DOM 状态提示播放不可用;纯键盘可操作且不移动焦点;Pause/Resume/Replay 两次后顺序与终态完全一致;?motion=static捕获两次像素稳定;打印与导出无控件无装饰 token。
七、影响与边界
ADR 0003 的最终裁决带来的后果清晰且可操作:
- 反模式的精确定义是"重复的或吸引注意力的自动播放",而非"交互之前出现的任何运动"。判断标准从"动没动"改为"会不会重复、是否在抢注意力"——
reveal的一次性有序解说因此完全合规; - 验证器零启发式:由于固定控制器是唯一能启动运行(start a run)的代码,且它恰好实现了"一次性 reveal"策略,
verify-motion.py无需任何基于时间或行为的 autoplay 探测,身份检查本身就是策略的执行; - 任何对 motion 行为(包括 reveal 语义)的修改都必须先改 template-motion.html 并评审,再逐字传播;手改控制器会被所有检查门禁拒绝(ADR 0001)。
需要判断某个动效诉求是否越界时,对照 semantic-patterns.md 中的静态回退规则即可:若一张图必须靠"滚动重播"才能看懂,说明静态帧本身没讲清楚,正确做法是回到静态设计,而不是放开 autoplay。
【免费下载链接】diagram-design38 editorial diagram types for Claude Code, Codex, and Pi. Self-contained HTML + SVG. No shadows. No Mermaid slop.项目地址: https://gitcode.com/GitHub_Trending/di/diagram-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考