wiggle-that-diagram:三种风格的解说动画
将一个静态的“工作原理”示意图转化为一个简短的、带字幕的循环动画,放入HTML幻灯片或报告中,并提供三种格式供读者选择。该方法基于一个真实的客户幻灯片项目(见“实际案例”)。它使用 huashu-art-motion 作为导演指导(分镜、镜头规则、六种失败方式),对于风格A和B,还将其作为渲染引擎。
何时使用,何时不使用
在以下情况下使用:
- 幻灯片或HTML报告中有机制图(文件流、规则检查、人员审批),而读者应该能够“亲眼看到”它逐步发生,并配有可读的文字说明;
- 用户希望获得真正不同格式的选项,而不是同一种动画的三种配色方案;
- 之前的动画“太快无法阅读”或需要添加字幕。
不要将其用于:单个微交互(使用 animate)、带旁白的视频(直接使用 huashu-art-motion)、或数字图表(使用 dataviz)。
三种风格(它们的区别在于格式,而非调色板)
| A · 图表片段 | B · 白板 | C · 实时SVG | |
|---|---|---|---|
| 画面 | huashu 引擎画面场景,t1_3b1b 语法,渲染为无文字H.264 | huashu y3_whiteboard 语法,深色背景,用笔绘制和手写文字(Kalam字体),方形视频 | 由虚拟摄像机拍摄的内联SVG世界(通过GSAP 3.12.5 补间 viewBox) |
| 画面上的文字 | 基于导出的摄像机轨道将HTML标签覆盖在视频上(始终清晰) | 绘制到视频中(在手机上必须足够大) | 真实的SVG文字(清晰,可根据宽度更改摄像机帧来重排) |
| 每个影片大小(实际案例) | 360–820 KB(预算900 KB) | 847–886 KB(预算900 KB) | 21–34 KB(预算40 KB) |
| 感觉像 | 一个干净的3Blue1Brown风格图表 | 有人在白板上讲解 | 一个精致的产品动画 |
| 主要风险 | 如果场景在未重新渲染的情况下发生变化,标签会漂移 | 严格的大小预算;手机标签 12–14 px | 繁琐的摄像机数学运算;GSAP变换怪癖 |
三者共享一份字幕脚本、相同的节拍和相同的嵌入契约,因此样式切换器可以展示用三种方式讲述的同一个故事。
方法,从端到端
每个步骤都以检查点结束,以证明该步骤已完成。
-
找到机制和一个具体示例。 阅读宿主页面(只读)。选取要展示的时刻,并在每个节拍中贯穿一个真实的示例(一行、一条记录、一个案例,及其真实值)。列出不得虚构的内容(ID、人物、图像)。→ 检查点:你可以将每个节拍描述为“这个对象做了这个”,包含名词和动词。
-
首先编写字幕脚本。 每个节拍一句字幕,每个影片3-6个节拍,保存在
beaks.json中(格式参考:scripts/common/beats.example.json)。字幕在所有风格中完全相同。然后运行节奏检查,并读取它打印的下限:python3 ~/.claude/skills/wiggle-that-diagram/scripts/common/pace_check.py beats.json→ 检查点:每个节拍都正常,并且用户已同意生成的循环长度(或更短的字幕)。切勿独立于字幕设置循环目标;请参阅“节奏”部分。
-
编写简报。 将
references/brief-template.md的内容填入SPEC.md:引用的反馈、令牌数、节奏规则、字幕脚本、节拍、包含完整代码片段的嵌入契约、预算、交付物。→ 检查点:一个没有其他上下文的智能体可以基于该简报进行构建。 -
方向选择轮次(可选,当用户尚未看到风格时)。 三个智能体并行工作,每种风格一个,分别制作同一影片的一个片段原型(约8–12秒),并在1280和390像素宽度下拍摄静态图。并排展示静态图;用户选择一个或保留全部三种。→ 检查点:用户的决定是书面形式的。
-
构建轮次。 每种风格一个智能体(各自继续自己的原型),构建该风格的所有影片,仅在
<work>/<style>/final/下写入。简报:references/agent-briefs.md。在启动前向用户说明智能体的数量和层级。→ 检查点:每个智能体返回文件、静态图、NOTES.md 和一个诚实的未解决问题列表。 -
构建配方(每个风格智能体实际执行的操作;脚本位于
scripts/style-a|b|c/): 两个中性影片以所有三种风格提供,每个包含beats.json加上style-a/、style-b/、style-c/:examples/pizza/(5个节拍,简单)和examples/expenses/(6个节拍:卡片、颜色编码的原因、指向第二个和第三个面板的箭头)。从对应风格的文件夹开始。- A.
cp -R ~/.claude/skills/huashu-art-motion/scripts/engine <A>/engine并执行touch <A>/engine/favicon.ico。添加engine/ways/way_make.js(来自scripts/style-a/),运行beats_js.py beats.json --engine engine --var <VAR>(字幕和时间来自共享脚本,绝不手动复制),创建一个场景文件engine/ways/<film>.js,调用WAY.make({poster, beats, camBase, cam, labels}),并注册SCENES['<film>'],以及engine/eras_<film>.js,设置FILM_SIZE=[1280,720]、PUNCH=0(否则引擎会在过渡时添加0.03的推近效果)、SCENE_LIBS=['ways/way_make.js','ways/<film>_beats.js','ways/<film>.js']、SCENE_DIR='ways'、ERAS=[{id:'<film>',dur:1}]。不要在画布上绘制文字。绘图辅助函数(圆角矩形、点、线、淡入淡出、沿路径移动)是针对每个影片编写的;examples/pizza/style-a/pizza.js包含一小套可复制的函数。然后执行render_way.py <film> --scale 0.75 --crf 30→build.py <film> --meta meta.json→pace_check.py work/<film>_data.json→test_embed.py final/<film>.html:<verdict beat>(或一次性完成:make.sh)。在任何场景编辑后,重新渲染并在中间移动时间点运行midmove.py:标签必须停留在其对象上。 - B. 以相同方式复制引擎(包含空的
favicon.ico:在真实Chrome下,其404错误被视为页面错误,render.py在写出正常视频后也会以退出码1退出)并将render.py修改为chromium.launch(channel='chrome', …)。将kit.js放入engine/demos/_hb/,编写engine/demos/<film>/board.js(使用HB.make(),将事件锚定到BEATS[k].t0 + offset)和engine/demos/<film>/eras.js(FILM_SIZE=[1080,1080],PUNCH=0,Kalam字体,SCENE_LIBS=['demos/<film>/beats.js','demos/_hb/kit.js','demos/<film>/board.js'])。然后执行build_beats.py beats.json --engine engine→uv run --with playwright python engine/render.py --film demos/<film> --fps 30 --crf 12 --out work/<film>_master.mp4→build_html.py <film> --beats beats.json(720²,24 fps,能容纳的最低CRF)→test_contract.py final/<film>.html --beats beats.json。每个影片最终都需要拉远到整个白板,保持≥1秒,然后擦除;空白白板是循环重新开始的地方。beats.json中最后一个节拍的still由所有风格共享,并且必须在擦除开始前完成,否则海报帧和减少动效模式下的帧将显示一个半擦除的白板。移动的对象(如滑板车、盒子)是K.custom项,带有handed: false,其位置是时间的函数;为每个对象指定一个结束时间,否则它在完成工作后会一直停留在白板上。 - C. 从
examples/pizza/style-c/(SVG、时间线、主题)开始。编写src/<film>.svg(一个 `<svg id=
- A.