关于 hand-drawn-cartoon
用于规划并制作 HyperFrames 手绘卡通短片的 Skill,涵盖创意简报、节拍安排、动画风格、渲染与检查,并指导制作循环视频。
主要功能
- 01创意简报与节拍规划
- 02可配置画幅、时长和基调
- 03SVG 与 GSAP 手绘动画指南
- 04文字可读性与动作时序规则
- 05快照检查和渲染清单
适用场景
- 01围绕一个笑点或创意制作短片
- 02按品牌规范调整角色配色
- 03规划节拍和屏幕文字
- 04检查并渲染循环动画
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phantomtonyt/ motion-design-skills --skill hand-drawn-cartoon安装后在 Claude Code 中输入/hand-drawn-cartoon确认已加载。
详细介绍
手绘卡通
一个小角色在纸面舞台上演绎一个点子。线条带有墨迹感,并微微鲜活,仿佛每隔几帧就重新描画一次。整体应当有手作般的温暖与趣味,就像一页会动的童话书。
选项
| 选项 | 默认值 | 说明 |
|---|---|---|
| format | vertical 1080×1920 | 或横向 1920×1080、正方形 1080×1080 |
| duration | 15 s | 10–45 s 效果最佳 |
| tone | playful | calm(停顿更长,缓动更柔和)、playful(默认)、slapstick(更夸张的挤压变形、更快的笑点、更多切镜) |
| character | a simple round character | 或使用用户自己的角色(匹配其轮廓和颜色) |
用户可以用日常语言提出这些要求:“竖屏,20 秒,滑稽夸张”。
简述(规划前回答)
- 用一句话说明这个点子或笑点是什么?
- 角色是谁,它想要什么?
- 首先出了什么岔子,转折或笑点是什么?
- 第一秒屏幕上出现什么(钩子)?
- 视频最后以哪句台词或标题收尾?
然后写一个节拍列表:时间、画面内容、角色动作、文字和声音。各段时长相加应等于目标时长。默认结构:钩子(1.5 s)、尝试并失败、点子、再次尝试、笑点、结束卡。
阅读清单(与节拍列表一同编写)
为每个节拍写出观众必须理解的一件事(“机器人不会飞”“缓存未命中”),以及这条信息需要清晰呈现多久。一次只呈现一条信息。文字时长下限:标签至少停留 0.8 s;句子完整显示后,每个词约需 0.3 s。如果文字无法在规定时长内读完,就删减文字,不要加快呈现速度。
动作规范
- 动作应有因果关系:角色会对画面中的事物做出反应,每个动作都有起因和结果。
- 角色绝不能静止不动:等待时也要呼吸、眨眼和调整重心。这些待机动作本身就是表演,而非填充内容,因此要小幅且不规则。
- 避免动作同步:双臂不要像镜像一样移动,眼睛、触角和身体也不要在同一帧开始动作。各部分错开 2–4 帧。
- 过冲和挤压变形应来自角色本身(落地、弹跳、惊讶时)。镜头和文字则使用平滑的缓出效果。
- 每个场景只保留一个主要移动方向,并在场景切换时让角色或道具延续移动,不要交叉淡化。
- 在笑点到来前加入 0.3–0.75 s 的定格,让笑点更有冲击力。
风格规则(始终遵守)
- 每条线都使用深暖色墨迹(近似
#2b1d16),在 1080 宽度下为 5–8 px,并使用圆角连接。绝不用纯黑。 - 线条应有轻微抖动:整个墨迹层使用 3 种略有差异的位移滤镜之一,每 2 帧(12 fps)切换一次。没有这种抖动,画面就会像干净的矢量图。
- 始终呈现纸张质感:暖白色底色、淡淡的颗粒叠层和柔和的暗角。
- 阴影使用蜡笔排线或一种纯色深色调。角色上不要使用渐变。
- 角色应圆润简洁:大头或大身体、点状或椭圆形眼睛并带白色高光、小嘴和短小四肢。动作要带动整个身体。
- 每个动作都要先有预备动作,然后移动,再在接触时挤压变形,最后稳定下来。
默认值与覆盖规则
优先级:用户的品牌规范或规格,其次是明确要求,最后才是这些默认值。如果用户提供了品牌色,就将其用于角色和一种强调色。墨迹、纸张和颗粒效果保持不变。
- 纸张
#efe6d2,浅色纸张#fbf6ea,墨迹#2b1d16,强调色#b8402f(标题、关键道具),天空晕染色#b9d8e8,太阳或点子黄色#f5c842,角色颜色#7fb6c9。 - 字体:标题使用 Caveat Brush,标签使用 Patrick Hand(Google Fonts)。9:16 画面中的标题字号为 80–110 px。
设置(仅需一次)
node -v && ffmpeg -version
npx hyperframes init my-video --non-interactive --resolution portrait && cd my-video # landscape for 16:9
在现有空文件夹中使用 npx hyperframes init . --non-interactive --resolution portrait,然后将 index.html 替换为你的合成作品。
制作路线
| 需求 | HyperFrames 中的 SVG + GSAP(默认) | p5.js + p5.brush(画布) |
|---|---|---|
| 带抖动效果的墨线轮廓 | 是(置换滤镜) | 是(重新设定种子的笔刷抖动) |
| 水彩晕染和干笔纹理 | 近似实现(颗粒、排线) | 是 |
| 无 GPU 时快速渲染 | 是 | 较慢 |
| 带有命名且可复用部件的角色 | 是 | 更难 |
默认使用 SVG + GSAP。只有当真实的颜料晕染是重点时才使用 p5.brush;这时,每一帧都要根据时间 t 绘制(不使用未固定种子的随机数)。
视觉语言
- **场景:**地面线、屋顶或桌沿,搭配一两个道具,以及天空或墙面。形状应略显不规则(使用手绘路径,而不是完美的矩形)。
- **景深:**设置远景层(太阳、云、山丘)、中景层(场景)和近景层(道具或从镜头前掠过的云)。通过移动世界群组来移动镜头;远景层移动幅度更小。
- **角色部件:**将身体、眼睛、嘴巴、左右手臂以及触角或耳朵分别放在独立群组中,让它们各自进行动作。使用包裹群组(
<g transform="scale(1.7)">)调整角色大小,并在外层群组上为 x、y 和旋转添加动画。缩放动画群组本身会导致位置偏移,因为 SVG 变换默认以左上角为原点。 - **挤压与拉伸:**以脚部为中心,并将
svgOrigin设为角色自身坐标中的脚部位置(例如svgOrigin: "0 6")。保持体积不变:高度增加 10% 时,宽度应缩窄约 5%。 - **效果:**尘土团(通过动画增大圆的
r属性并淡出)、绕圈的眩晕星星、速度线、闪光。所有这些效果也都要画上墨线。
| 动作 | 时长 |
|---|---|
| 预备挤压 | 0.1–0.12 s |
| 向上跳 | 0.4–0.5 s,power2.out |
| 顶点处的漫画式停留 | 0.3–0.5 s |
| 下落 | 0.25 s,power3.in |
| 落地挤压 | 0.08 s,然后在 0.5 s 内弹性回稳 |
| 眨眼 | 0.12 s,每 2–4 s 一次,间隔不规则 |
| 歪头 | 0.3 s,8–12° |
让画面引人注目
- 第一帧就是完整画面:场景、角色、天空,以及手绘卡片上的标题。
- 每个节拍都要有东西在动:鸟、缓慢转动的太阳、飘动的云、晃动的触角、眨眼。
- 用镜头呈现一个重要时刻:跟随角色向上或向侧面移动,并配合视差层。
- 用一种强调色突出关键道具和标题中的关键词,其他部分则采用柔和色调。
- 笑料要能从剪影中看懂:失败时要有完整的挤压效果,并伴随尘土和星星;成功时则要摆出清晰醒目的姿势。
- 最后一帧要与第一帧一致(取景和姿势相同),这样视频才能循环播放。
- 每一帧定格画面都应值得发布。如果截图看起来空洞或平淡,就说明还没做好。
入门模板
<div id="root" data-composition-id="main" data-start="0" data-duration="15" data-width="1080" data-height="1920">
<svg viewBox="0 0 1080 1920" width="1080" height="1920">
<defs>
<filter id="boil0"><feTurbulence type="fractalNoise" baseFrequency="0.03" numOctaves="2" seed="2"/><feDisplacementMap in="SourceGraphic" scale="4"/></filter>
<filter id="boil1"><feTurbulence type="fractalNoise" baseFrequency="0.03" numOctaves="2" seed="9"/><feDisplacementMap in="SourceGraphic" scale="4"/></filter>
<filter id="boil2"><feTurbulence type="fractalNoise" baseFrequency="0.03" numOctaves="2" seed="17"/><feDisplacementMap in="SourceGraphic" scale="4"/></filter>
<filter id="grain"><feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="3" seed="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 .3 0 0 0 0 .24 0 0 0 0 .16 0 0 0 .2 0"/></filter>
<pattern id="hatch" width="14" height="14" patternUnits="userSpaceOnUse" patternTransform="rotate(35)"><line x1="0" y1="0" x2="0" y2="14" stroke="#2b1d16" stroke-width="3" stroke-opacity=".28"/></pattern>
</defs>
<rect width="1080" height="1920" fill="#efe6d2"/>
<g id="world"><g id="ink" filter="url(#boil0)" stroke="#2b1d16" stroke-width="7" stroke-linejoin="round" stroke-linecap="round">
<path d="M-60 1312 L770 1304 Q810 1306 812 1330 L800 2200 L-60 2200 Z" fill="#c9b79a"/>
<g id="hero"><g transform="scale(1.7)">
<g id="body"><circle cx="0" cy="-100" r="92" fill="#7fb6c9"/><path d="M-92 -100 a92 92 0 0 0 184 0" fill="url(#hatch)" stroke="none"/>
<rect x="-58" y="-128" width="116" height="62" rx="30" fill="#fbf6ea"/>
<g id="eyes" stroke="none" fill="#2b1d16"><ellipse cx="-24" cy="-98" rx="11" ry="15"/><ellipse cx="24" cy="-98" rx="11" ry="15"/></g>
<g id="armR"><path d="M88 -92 q40 10 52 46" fill="none" stroke-width="12"/></g></g>
</g></g>
</g></g>
<rect width="1080" height="1920" filter="url(#grain)"/>
</svg></div>
<script>
const tl = gsap.timeline({ paused: true });
for (let i = 0; i < 90; i++) tl.set("#ink", { attr: { filter: `url(#boil${i % 3})` } }, i / 6); // boil on twos
gsap.set("#hero", { x: 400, y: 1316 }); // feet on the stage
tl.to("#body", { scaleY: 0.85, scaleX: 1.1, svgOrigin: "0 6", duration: 0.1, yoyo: true, repeat: 1 }, 1.7); // anticipation
tl.to("#hero", { y: 956, duration: 0.45, ease: "power2.out" }, 1.95).to("#hero", { y: 1316, duration: 0.25, ease: "power3.in" }, 2.9);
tl.to("#body", { scaleY: 0.5, scaleX: 1.35, svgOrigin: "0 6", duration: 0.08 }, 3.15)
.to("#body", { scaleY: 1, scaleX: 1, svgOrigin: "0 6", duration: 0.5, ease: "elastic.out(1,0.4)" }, 4.3);
tl.to("#armR", { rotation: -60, svgOrigin: "88 -92", duration: 0.16, yoyo: true, repeat: 5 }, 13.6); // wave
tl.set({}, {}, 15);
window.__timelines["main"] = tl;
</script>
添加一个 <head>,其中包含 GSAP 脚本(https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js)、Google Fonts 链接,以及 html,body{margin:0;width:1080px;height:1920px;overflow:hidden}。将节拍列表中的动作依次扩展到同一个 tl 上。
需要公开的参数
format · duration · tone · characterColor · accent · paper · title · endLine · beats。将它们作为常量放在脚本顶部。
避免
- 完美的几何形状和干净、没有抖动的线条(会显得像扁平矢量图)。
- 在角色上使用渐变、辉光和投影。
- 角色太小,淹没在空白中。角色高度应占画面高度的 25–45%。
- 在角色脸部上叠加文字,也不要在 9:16 画面的底部 670 px 或顶部 270 px 区域放置文字。
- 使用
Math.random()或依赖一天中的时间。抖动效果和其他特效必须由时间轴驱动。
故障模式
| 表现 | 原因 | 修复方法 |
|---|---|---|
| 画面干净得像矢量图,不像手绘 | 没有抖动,形状过于完美 | 每隔 2 帧切换 3 个位移滤镜;使用手绘路径 |
| 缩放时角色漂移或下沉 | 缩放动画加在移动组上 | 将尺寸设置在内层 <g transform="scale()"> 上;使用 svgOrigin 以双脚为基准压缩 |
| 效果从角落开始 | 效果出现前未设置 x/y | 在时间 0 使用 gsap.set 设置起始位置 |
| 画面浑浊、对比度低 | 中间色调过多 | 纸张色调明亮,墨色深沉,只用一种强调色 |
| 动作僵硬 | 动作没有预备或收势 | 先加入压缩动作,再加入弹性收势 |
| 堆叠标题出现文字重叠警告 | 手写字体的行框相互接触 | 行间增加 10–20 px;通过快照确认 |
验证
npx hyperframes check
npx hyperframes snapshot --at <one time per beat, plus one mid-transition>
npx hyperframes render -o renders/<name>.mp4
查看每一张快照和 snapshots/contact-sheet.jpg:
- 间隔 1/6 秒的两张快照中,线条明显不同(抖动有效)
- 角色站在舞台上(没有下沉),且在手机尺寸下清晰可辨
- 每个节拍的剪影都能表达清楚;失败和结果呈现足够突出
- 文字位于安全区域内;过渡过程中没有任何内容碰撞
- 最后一帧与第一帧一致
最终渲染前的自我检查
根据以下方面给快照表打 1–10 分:吸引力(第一秒)、手机屏幕宽度下的可读性、动作质量、构图、准确性,以及“静音测试”(观众能否在没有声音的情况下用一句话概括内容?)。只修复得分为 7 分或以下的项目,然后重新生成快照并重复检查,直到所有项目都达到 8 分或以上。如果可以,请让另一个只读检查流程负责评分,这样就不会给自己的作品打分。
| 不要这样做 | 应该这样做 |
|---|---|
| 在两个内容繁杂的布局之间交叉淡化 | 先移除旧内容,再引入新内容;或者让一个物体贯穿其中 |
| 同时让多件事动起来 | 错开动作,每个时刻只突出一个焦点 |
| 用漂浮或摇晃填充停顿 | 保持静止,或让角色做出动作 |
| 为了营造“活力”而添加粒子、彩虹渐变或 3D 翻转 | 加入更有力的事件:揭示、撞击或镜头移动 |
| 为了赶时间而压缩文字 | 删减文字 |
交付
- 选取最有力的定格画面作为海报:
ffmpeg -ss <t> -i renders/<name>.mp4 -frames:v 1 poster.jpg。将其嵌入为第 0 帧,让缩略图显示该画面:ffmpeg -i renders/<name>.mp4 -i poster.jpg -filter_complex "[0:v][1:v]overlay=enable='eq(n,0)'" -c:a copy renders/<name>-final.mp4。 - 撰写 1–2 句说明,点明创意,而不是技法。
- 告诉用户每个节拍中发生了什么,以及修改时要调整哪个参数。
参考作品
- @other__reality,《I'm Upping My P(doom)》音乐视频,使用 p5.js + p5.brush 以代码制作:https://x.com/other__reality/status/2102514581684052169(公开配方:github.com/JohnHeibel/PDoomVideo)
- @devteamdrew,将 Claude 的每种努力程度呈现为故事书插曲:https://x.com/devteamdrew/status/2105353836638744578
- @shfred0,《The Life of Opus》,使用 JavaScript 生成的程序化笔触:https://x.com/shfred0/status/2102495989194236158
- @akokoi1,完全用代码制作的水墨风故事书:https://x.com/akokoi1/status/2102699703309898026
- 改编创意(非照搬)自 HyperFrames 动效准则和无真人出镜解说技能、JohnHeibel/ClaudeAnimationBase 的动画指南、howseen-ai/claude-motion-design 的评审循环,以及 latent-spaces/brag。