关于 whiteboard-animation
whiteboard-animation 是利用 SVG 路径制作手绘白板动画的技能,适合创作讲解内容的制作者。它涵盖逐笔绘制、笔尖跟随、按旁白编排元素,以及独立 HTML 或 Remotion 视频输出。
主要功能
- 01SVG 路径逐笔绘制
- 02手持笔尖跟随绘制位置
- 03按旁白顺序编排场景
- 04手写动画与画板清除转场
- 05输出独立 HTML 或 Remotion 视频
适用场景
- 01制作白板手绘解说视频
- 02呈现草图或 Logo 的绘制过程
- 03让书写标签与旁白同步
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ iart-ai/ explainer-video-skills --skill whiteboard-animation安装后在 Claude Code 中输入/whiteboard-animation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/draw-on-recipes.mdwhiteboard-pipeline.md详细介绍
白板动画
VideoScribe / Doodly 风格:一只手在白板上逐笔绘制插画和手写文字,同时旁白进行讲解。通过会自行绘制的动画 SVG 路径和跟随绘图笔尖的马克笔手部来实现,然后按旁白顺序排列各个元素,让内容逐步填满白板。
适用场景
- 制作“手绘讲解”或涂鸦风格的说明视频,让一只手在白板上绘制艺术图案和文字。
- 动画呈现草图或徽标的实时绘制过程,逐线展现。
- 制作与配音同步的自动手写效果。
- 将静态插画转变为逐笔呈现的引导式展示。
核心机制:SVG 描边绘制效果
通过将 stroke-dashoffset 从路径的完整长度动画到 0,让路径“自行绘制”:间隙逐渐缩小,描边便从起点到终点逐渐显现。
const path = document.querySelector("#stroke");
const len = path.getTotalLength();
path.style.strokeDasharray = len; // one dash as long as the whole path
path.style.strokeDashoffset = len; // pushed fully out of view = invisible
path.getBoundingClientRect(); // force reflow before transitioning
path.style.transition = "stroke-dashoffset 1.2s linear";
path.style.strokeDashoffset = "0"; // draws start → end
在 <path> 上使用 pathLength="1" 进行归一化:这样,stroke-dasharray:1; stroke-dashoffset:1 就能绘制任意路径,无论其实际长度如何;单个缓动值(0→1)就是绘制进度。使用 linear(或非常轻缓的缓动)会显得运笔平稳;长笔画应避免使用 ease-in-out,否则马克笔会显得一顿一顿。
马克笔手部跟随笔尖
这种效果的成败取决于手部是否准确位于正在绘制的点上。根据当前绘制进度采样路径,并在每一帧将手部图像放到该位置;手部素材的笔尖必须与路径点对齐(调整图像偏移,让笔尖而非图像角落落在该点上)。
const hand = document.querySelector("#hand"); // <img>/<g>, tip at its top-left "nib"
const NIB = { x: 6, y: 4 }; // nib offset inside the hand art
function drawOn(path, hand, dur = 1200) {
const len = path.getTotalLength(), t0 = performance.now();
path.style.strokeDasharray = len;
(function frame(now) {
const k = Math.min(1, (now - t0) / dur); // 0→1 progress
path.style.strokeDashoffset = len * (1 - k); // expose start→tip
const p = path.getPointAtLength(len * k); // current tip in SVG coords
hand.setAttribute("transform", `translate(${p.x - NIB.x} ${p.y - NIB.y})`);
if (k < 1) requestAnimationFrame(frame);
})(t0);
}
getPointAtLength(len * k) 是关键:它会返回与驱动 strokeDashoffset 的进度相对应的路径坐标,让笔尖紧贴不断延伸的描边。在不同笔画之间抬起手部(淡出后跳到下一个起点),避免它在空白白板上滑动。
显现顺序——逐步填满白板
按照人物绘制场景的方式,依据旁白顺序排列元素:每个插画和手写文字块依次绘制、停留,然后开始下一个元素。在同一幅图中错开各笔画的开始时间;每个元素绘制完成后停顿片刻,再移动手部。一次绘制对应旁白中的一个节拍。不要同时绘制两个元素——观众(以及唯一的一只手)一次只能跟随一个笔尖。
| 步骤 | 机制 |
|---|---|
| 绘制一笔 | stroke-dashoffset 从 len→0,手部位于 getPointAtLength(len*k) |
| 多笔画图案 | 错开各笔画;手部跳到每一笔的新起点 |
| 手写文字 | 按阅读顺序逐个绘制字母或单词的笔画,并让手部跟随 |
| 停留 | 元素完成后,让手部离开画板并暂停(0.4–1.0s) |
| 下一个元素 | 将手部移至下一个起点或淡出后再移入;开始绘制该元素 |
| 擦除 / 清除 | 擦除或反向绘制,清空白板以展示下一部分 |
将图稿转换为可绘制路径
绘制效果需要按绘制顺序排列的单笔开放路径,而不是填充的复合形状。
- 将线稿制作或描摹为开放笔画(在 Illustrator/Inkscape 中使用轮廓,而非填充)。填充的色块没有可供动画呈现的“描边”;请将填充转换为中心线描边,或绘制轮廓路径,并在轮廓绘制完成后再填充。
- 顺序很重要:
<path>元素应在 SVG 中按照手部的绘制顺序排列,并按文档顺序为它们制作动画。 - 连续的长线条看起来最自然;只在现实中需要抬笔的地方断开。
- 对于纯色区域:先绘制轮廓描边,再在其后淡入或渐显填充(使用
opacity或裁剪揭示),这样看起来才像是“描完轮廓后再填色”。 - 手写文字:使用描边字体或手写字体,将字形转换为路径,然后按笔画顺序绘制每个字形的路径。也可以模拟:遮罩文字并从左向右扫动显现,同时让手部沿着遮罩边缘移动。
马克笔手部素材
使用一张手握马克笔的 PNG/SVG 图片,并确定笔尖的位置。只需测量一次笔尖偏移量(上文的 NIB),之后重复使用。整部作品使用同一手部素材,以保持一致。只有在需要左手时才将其镜像(scaleX(-1))。在手部下方添加淡淡的阴影以增加立体感;停留时手部“离开”画板,应隐藏阴影。
擦除/擦除转场
在不同章节之间清空画板,避免内容堆得太满:
- 擦除:让
clip-path(或白色矩形)横向扫过已填充的组,将其擦除;可选地让手做出「擦除」动作。 - 反向绘制:将
strokeDashoffset从 0→len 播放,让笔迹逐渐消失,手向后跟随。 - 淡出并重绘:让旧组淡出,同时开始绘制下一组。
保持统一的规则(例如始终从左向右擦除),让转场显得有意为之。
与旁白同步节奏
绘制时长应与口播时长同步,而不是使用固定计时器。按 VO 每秒约 2.3 个单词估算;每次绘制的总时长应大致等于所对应台词的时长,再加上短暂的停留。VO 说出某个标签时,该标签应恰好绘制完成。强调性台词时放慢手的速度;次要的衔接笔画则加快。旁白继续时,不要让手停在已绘制完成的画板上——让它移开,或开始绘制下一个元素。
输出检查清单
- 每条笔画都是按绘制顺序绘制的单一开放路径;不要让任何内容以完整形态突然出现。
- 每条笔画绘制期间,手的笔尖都要精准停在绘制前端(不得悬空或滞后)。
- 不同笔画/元素之间,手要干净地抬起;不要在空白画板上滑动。
- 按旁白顺序逐个绘制元素;每个元素绘制完成后都要停留一段时间,再绘制下一个。
- 手写内容按阅读顺序绘制;填充效果在轮廓完成后出现。
- 擦除规则保持一致;不要让画板内容过于拥挤。
- 绘制时长与所对应的 VO 台词同步。
prefers-reduced-motion:显示绘制完成的画板(所有笔画的偏移量均为 0,隐藏手),不播放绘制动画。
交付与验证(独立 HTML)
打包的辅助脚本(
scripts/):scripts/seek-shot.sh anim.html 0 1.5 3会冻结?t=N测试框架并截取每个时刻的屏幕截图;scripts/contact-sheet.sh sheet.png frame-*.png会将它们拼成一张图,便于快速浏览。参见scripts/README.md。
对于自包含的白板场景(绘制动画插图、手写字迹、简短的书写动画),交付物应为一个可在浏览器中直接打开的 HTML 文件——无需构建步骤,也无需渲染管线。单文件是网页动画的合适选择;一个文件就能完成的事,不必动用打包工具。(对于配有内嵌 VO 的完整解说式书写视频,请将其构建为 Remotion 合成,并通过 remotion still 验证——参见 explainer-video。)
输出约定:
- 一个
.html文件:内嵌 SVG(按绘制顺序排列路径,使用pathLength="1"或测量路径长度)、内嵌手部素材或将其设为 data-URI,并在一个内联<script>中放入绘制动画驱动代码。 - 所有绘制都在同一个主时间轴上(GSAP
tl,或单个 rAF 时钟),这样整个序列只有一个可跳转的播放头——strokeDashoffset和手部位置都由同一个进度值驱动。 - 加入下方的跳转测试框架,以便将任意时刻冻结并截图。冻结在
t时会显示路径实际绘制到的程度——验证依赖于此。
跳转测试框架——冻结精确时刻以便截图。 ?t=N 会将主时间轴跳转到 N 秒并暂停,因此截图会得到一帧静止且确定的中途绘制画面。
<script>
// ... build your master timeline as `tl` (drawOn calls placed on it) ...
const t = new URLSearchParams(location.search).get("t");
if (t !== null) { tl.pause(); tl.seek(parseFloat(t)); } // frozen at t seconds
// no ?t → plays normally
window.__ready = true; // ready signal for headless wait
console.log("duration", tl.duration());
</script>
验证循环——渲染 → 冻结 → 截图 → 检查:
- 在时间轴上的多个时刻打开文件——开始、中途绘制的一帧、结束:
…/scribe.html?t=0、?t=<dur/2>、?t=<dur>。在控制台读取tl.duration(),获取结束时间。至少选一个处于笔画中段的t——手尖贴合笔尖的错觉是否成立,关键就在此处。 - 对每个冻结帧进行无头截图:
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/scribe.html?t=1.2" frame-mid.png - 检查每张静帧——确认准确度:在笔画中段的
t,手的笔尖要恰好位于已绘制部分的前端(不能超前、落后或悬空);路径只绘制到笔尖位置;在?t=<dur>时,每一笔都应干净利落地完成,没有间隙、过冲或残留虚线。检查瑕疵:手在笔画之间滑过空白白板、笔尖偏移不正确(笔尖悬离线条)、笔画顺序错误、轮廓绘制完成前填充就出现、绘图被裁切或超出画布,以及字体加载前的 FOUC。 - 迭代调整:修改
NIB、笔画顺序和时长,然后重新截图,直到每一帧都像是手正在绘制。
完成前:
- 可在浏览器中独立打开——无控制台错误,也无缺失的 CDN 或素材。
- 使用一个主时间轴;
?t=N能正确冻结,且strokeDashoffset与手部位置共用一个进度值。 - 已截取开始/笔画中段/结束时的画面——手的笔尖始终贴合笔画前端,笔画在结束时干净利落地完成,绘制顺序正确。
- 笔画之间手会抬起;不会在空白白板上滑动。
prefers-reduced-motion会显示绘制完成的白板(所有笔画已绘制,手部隐藏),不播放绘制动画。
如需导出带旁白且帧确定的视频,请将相同的绘制动画(从 useCurrentFrame()/fps 驱动 strokeDashoffset 和手部变换)移植到 Remotion 合成中,并渲染为 MP4/GIF——渲染静帧 → 编码的流程参见 explainer-video 技能。
参考文件
references/draw-on-recipes.md— 可运行的绘制动画机制:getTotalLength/getPointAtLength实现手部跟随路径、GSAPDrawSVGPlugin和MotionPathPlugin变体、多笔画错开、逐字手写、先轮廓后填充、擦除/擦拭及反向绘制过渡,以及 Remotion 帧驱动移植方案。references/whiteboard-pipeline.md— 将美术素材转换为可绘制的单笔画 SVG、编写绘制顺序、手写字体/笔画字体、马克笔手部素材与笔尖校准、根据 VO 台词调整绘制时长,以及从分镜到白板的构建流程。