关于 kinetic-typography
Kinetic Typography 是用于制作以文字为画面主体的节奏型视频技能,适合将短语和重点词转化为动态视觉的创作者。它围绕节拍、字词动作和强对比版式规划动画。
主要功能
- 01短语与重点词拆分
- 02按语音或音乐节奏排时
- 03文字弹入、滑动、翻转等动作
- 04高对比色彩与字体指导
- 05循环结尾与画面检查
适用场景
- 01制作节奏鲜明的标题短片
- 02将旁白或歌词转为动态文字
- 03为社交视频制作片尾标题版式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phantomtonyt/ motion-design-skills --skill kinetic-typography安装后在 Claude Code 中输入/kinetic-typography确认已加载。
详细介绍
动态排版
制作让文字本身成为画面的影片。一句话落下、填满画面、受到冲击,随后随着节奏被下一句话替换。整体应当大胆、迅速且富有音乐感,就像一张会动的海报。
选项
| 选项 | 默认值 | 备注 |
|---|---|---|
| format | vertical 1080×1920 | 或横屏 1920×1080、方形 1080×1080 |
| duration | 10–20 s | 与旁白或歌曲片段时长匹配 |
| tone | punchy | calm(幻灯片式切换和 0.4 s 淡入淡出,翻转更少)、punchy(默认)、chaotic(全大写、猛然出现、抖动,每隔一句就翻转一次) |
| rhythm | fixed tempo | 或旁白词语时间点,或音乐节拍 |
简述(规划前回答)
- 观众应该记住的那句话是什么?
- 将脚本拆分为每段 1–4 个词;每段标出一个主打词。
- 能量在哪一刻达到顶峰(触发颜色翻转的那句话)?
- 结尾组合画面是什么(标题、行动号召、账号名)?
阅读时长表(与节拍列表一起编写)
为每个节拍写明观众必须理解的一件事(“机器人不会飞”“缓存未命中”),以及这段内容保持可读的时长。一次只呈现一项。文字时长下限:标签至少停留 0.8 s;句子完全显示后,每个词约需 0.3 s。如果文字来不及读完,就删减文字,不要加快速度。
动效准则
- 动效要有因果关系:画面上的每个动作都有缘由(点击、坠落、数据包抵达),后续动作也由此引出。
- 为整段影片选定一个主要运动方向,并始终保持一致。其他方向用于传达含义:向上表示成功或结论,缩小表示“呈现全貌”。
- 前一幕的退出要为下一幕的进入做铺垫:使用相同轴线、相同方向和相近速度。让一个物体贯穿转场,而不是使用交叉淡化。
- 图表、图形或文字不要添加无意义的填充动效(漂浮、呼吸、摇晃)。如果没有东西需要移动,就保持静止;一次有意的镜头漂移就足够了。
- 缓动:进入时使用平滑的缓出(
power2.out至power4.out),退出时加快缓入(时长约为进入的 75%)。进入动效最多 0.8 s。一组元素的总错开时长最多 0.5 s。back.out超调仅用于轻微弹出;不要使用弹性或反弹效果。 - 关键时刻一次只动一个元素;观众的视线一次只能跟随一个对象。
- 在揭晓前加入 0.3–0.75 s 的静止节拍。
风格规则(始终遵守)
这些规则构成了这种风格;无论颜色或文字如何变化,都应遵守。
- 字体宽度占画面宽度的 50–80%,因为小字看起来像字幕,而不是视觉主体。
- 屏幕上一次只呈现一个想法:1–4 个词,最多 3 行。其他内容都应离开画面。
- 每个词出现时都要伴随动作(猛然出现、滑入、拉伸、遮罩揭示、翻转)。不要单纯淡入。
- 切换要落在节奏上:每隔 0.25–0.7 s 出现一个新词或动作,关键词要落在节拍或重读音节上。
- 对比度要强烈:近黑色搭配米白色,或反过来,再加一种仅用于关键词的强调色。
- 每段都要有一个主打词:尺寸最大、使用强调色,或以不同方式动效呈现。
默认值(可自由更改)
字体、颜色、文字、时间安排和背景都可以更改。如果用户提供了品牌字体或颜色,以用户提供的为准。优先级:用户的品牌规范或规格要求,其次是明确请求,最后是这些默认值。
- 背景
#111111,文字#f4f1ea,强调色#ff4d2e。可按每段内容切换为浅色文字配深色背景,或深色文字配浅色背景,增强活力。 - 展示字体:厚重的窄体字(Anton、Bebas Neue 或 Archivo Black);主打词使用全大写。辅助字体:厚重的无衬线字(Inter 800),用于较小的连接词。
- 字号:9:16 画面中主打词为 180–320 px;小字为 60–90 px。
设置(一次性)
node -v # needs 22+
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 替换为下面的模板。
构建前先确定
写代码前,先用一行向用户确认以下内容。
- **画幅:**9:16(1080×1920)或 16:9(1920×1080)。默认采用 9:16。
- **文案与时长:**将脚本拆分为每段 1–4 个词。有配音时,根据配音的词语时间戳确定时长(
npx hyperframes transcribe voice.wav)。没有配音时,每段时长设为 0.6–1.4 秒,重点词语的时长要更长。 - **节奏来源:**配音(跟随重读音节)、音乐(跟随节拍;
npx hyperframes beats music.mp3)或固定速度(例如 120 BPM = 每 0.5 秒一个节拍)。 - 每段的重点词语以及对应的动效。
| 需求 | HyperFrames + GSAP(默认) | Remotion |
|---|---|---|
| 词语和字母动效、遮罩、拉伸 | 是 | 是 |
| 根据配音或节拍数据确定时长 | 是(标签) | 是(帧) |
| 仅需 Node 和 FFmpeg 即可运行 | 是 | 是 |
| 许可 | 免费 | 最多供 3 人免费使用,超过需付费 |
动效词汇表
| 动效 | 实现方式(GSAP) | 适用场景 |
|---|---|---|
| 猛击 | from {scale: 2.6, opacity: 0},expo.out,0.22 秒,并添加持续 0.1 秒的 6 px 画面抖动 | 重点词语 |
| 叠层滑入 | 在 overflow:hidden 遮罩内,将每行设为 from {yPercent: 110},错开 0.06 秒,使用 power4.out,持续 0.35 秒 | 列表、两行短语 |
| 拉伸 | scaleY: 1.6 → 1,transformOrigin: bottom,使用 back.out(3),持续 0.3 秒 | 强调、动词 |
| 字母级联 | 将字母设为 from {y: -120, rotation: -20, opacity: 0},错开 0.025 秒,使用 back.out(2) | 名称、标题 |
| 翻转替换 | 旧词通过 rotationX: 90 翻出(0.12 秒),新词通过 rotationX: -90 → 0 翻入(0.18 秒) | X 变为 Y |
| 分裂 | 将词语的两半向两侧滑开(x: ±140),露出中间较小的词语 | 对比 |
| 退场 | 使用 yPercent: -110 或 scale: 0.7, opacity: 0,持续 0.15 秒,采用 power2.in | 每段退场时 |
退场要快(0.12–0.18 秒),入场要比你想象的更利落(0.18–0.35 秒)。每段落定后至少停留 0.35 秒,以便观众读完。
打造吸睛效果
- **铺满画面。**根据画面宽度而不是字体大小调整词语尺寸。采用 250+ px 的窄体字体,呈现目标风格。
- **颜色切换:**在 2–3 段重点短语出现时,背景在深色、浅色(或强调色)之间切换,并在节拍上硬切。
- **纹理与纵深:**叠加微妙的颗粒效果,为重点词语添加柔和阴影或错位副本(强调色,偏移 10–14 px),并偶尔在背景中放置一个巨大、淡化的词语。
- **镜头动感:**让整个舞台缓慢缩放 2–4%,重点词语出现时快速推近(缩放至 1.06,持续 0.1 秒),猛击时抖动 6–10 px。
- **文字搭配图形:**添加绘制入场的下划线条、在词语后弹出的圆形、箭头等。使用纯色强调色。
- **结尾定格画面:**完整呈现标题或行动号召,停留 1–1.5 秒,并与开场画面相呼应,以实现循环播放。
- **质量门槛:**如果任何快照中出现纯色背景上的小号居中文字,就说明还没做好。
构建:模板
<!doctype html><html><head><meta charset="UTF-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Anton&family=Inter:wght@800&display=swap">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<style>
html,body{margin:0;width:1080px;height:1920px;overflow:hidden;background:#111}
#root{width:100%;height:100%;position:relative;overflow:hidden}
#stage{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}
.phrase{position:absolute;left:60px;right:120px;top:270px;bottom:670px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:0}
.line{overflow:hidden;line-height:.92;padding:0 8px}
.w{display:inline-block;font-family:Anton,sans-serif;text-transform:uppercase;color:#f4f1ea;white-space:nowrap}
.hero{color:#ff4d2e}
.flip .w{color:#fff}.flip .hero{color:#111}
.small{font-family:Inter,system-ui,sans-serif;font-weight:800;text-transform:none}
#bg{position:absolute;inset:0;background:#111}
</style></head>
<body><div id="root" data-composition-id="main" data-start="0" data-duration="8" data-width="1080" data-height="1920">
<div id="bg"></div><div id="stage"></div></div>
<script>
// [time, lines, moveName, heroWordIndex, flipBackground?] -- edit this list; times in seconds
const PHRASES = [
[0.2, ["WORDS"], "slam", 0],
[1.0, ["can", "MOVE"], "stack", 1],
[2.0, ["LIKE", "MUSIC"], "stretch", 1, true],
[3.2, ["one", "idea"], "stack", 1],
[4.2, ["AT A", "TIME"], "slam", 1],
[5.4, ["KINETIC", "TYPE"], "cascade", 1],
];
const END = 8, W = 1080 - 180;
// measure text only after the web fonts load, then build and register the timeline
document.fonts.ready.then(() => {
const tl = gsap.timeline({ paused: true });
const stage = document.getElementById("stage");
PHRASES.forEach(([t, lines, move, hero, flip], i) => {
const p = document.createElement("div"); p.className = "phrase" + (flip ? " flip" : ""); stage.appendChild(p);
const words = lines.map((txt, k) => {
const line = document.createElement("div"); line.className = "line";
const w = document.createElement("span"); w.className = "w" + (k === hero ? " hero" : "") + (txt === txt.toLowerCase() && k !== hero ? " small" : "");
w.textContent = txt; line.appendChild(w); p.appendChild(line);
w.style.fontSize = "100px"; const fit = Math.min(420, (100 * W) / w.getBoundingClientRect().width); // fill the width
w.style.fontSize = (w.classList.contains("small") ? Math.min(fit, 110) : fit) + "px";
return w;
});
gsap.set(p, { autoAlpha: 0 });
tl.set(p, { autoAlpha: 1 }, t);
if (move === "slam") tl.from(words, { scale: 2.6, opacity: 0, duration: 0.22, ease: "expo.out", stagger: 0.08 }, t)
.fromTo("#stage", { x: -10 }, { x: 10, duration: 0.04, yoyo: true, repeat: 3, ease: "none" }, t + 0.18).set("#stage", { x: 0 }, t + 0.34);
if (move === "stack") tl.from(words, { yPercent: 110, duration: 0.35, ease: "power4.out", stagger: 0.07 }, t);
if (move === "stretch") tl.from(words, { scaleY: 1.7, opacity: 0, transformOrigin: "50% 100%", duration: 0.3, ease: "back.out(3)", stagger: 0.08 }, t);
if (move === "cascade") words.forEach((w, k) => {
const letters = Array.from(w.textContent); w.textContent = "";
letters.forEach((c) => { const l = document.createElement("span"); l.style.display = "inline-block"; l.textContent = c; w.appendChild(l); });
tl.from(w.children, { y: -140, rotation: -20, opacity: 0, duration: 0.4, ease: "back.out(2)", stagger: 0.03 }, t + k * 0.15); });
const next = PHRASES[i + 1] ? PHRASES[i + 1][0] : null;
if (flip) tl.set("#bg", { background: "#ff4d2e" }, t).set("#bg", { background: "#111" }, next || END); // colour flip on a key phrase
if (next) tl.to(words, { yPercent: -110, opacity: 0, duration: 0.15, ease: "power2.in", stagger: 0.03 }, next - 0.17)
.set(p, { autoAlpha: 0 }, next);
});
tl.fromTo("#root", { scale: 1 }, { scale: 1.04, duration: END, ease: "none" }, 0); // slow drift
tl.set({}, {}, END);
window.__timelines["main"] = tl;
});
</script></body></html>
修改 PHRASES 以更改词语和时长。要添加动效,可根据动效词汇表添加 if (move === ...) 分支。有配音时,将 <audio> 元素放在根节点中,并根据配音的转录结果设置每段时长。
向用户公开的参数
aspect · phrases(时间、行、动效、重点)· fontDisplay · fontSupport · colors(bg、text、accent)· tempo or audio · flipPhrases(背景切换的位置)· endLockup。
避免
- 将淡入和缓慢漂移作为主要动效。这看起来像幻灯片。
- 同时显示超过 4 个词或 3 行文字。
- 使用多种强调色。只用一种强调色,且仅用于重点词语。
- 使用笔画纤细或字重较轻的字体,或用手写字体显示重点词语。
- 在 9:16 画面的底部 670 px 或顶部 270 px 区域放置文字(平台 UI 会遮挡这些区域)。
- 使用随机变化或逐次渲染变化。所有数值都必须由时间轴决定。
失败模式
| 看起来像 | 原因 | 修正方法 |
|---|---|---|
| 幻灯片 | 淡入淡出、长时间停留、每次都用同一种运动 | 从动作词汇中变换动作;入场动画控制在 0.35 s 以内;退场动画控制在 0.15 s |
| 字幕,而非设计 | 字太小,居中放在空白处 | 字词大小要适配画面宽度;使用窄体字体;至少 250 px |
| 杂乱难读 | 同时出现太多字词或动作 | 屏幕上只显示一个短语;设置一个主角词;落位后停留 0.35 s |
| 节奏不合 | 靠猜测确定短语时间 | 使用转录文本或节拍时间;在重拍前 2–3 帧落位 |
| 字词被遮罩裁切 | 行高对字体来说太紧 | 将 .line 的 line-height 设为 0.92–1.0,并添加内边距;检查快照 |
| 渲染时字体错误 | 字体未加载 | 在 head 中添加 Google Fonts <link>;检查快照,确认显示的是窄体字体 |
最终渲染前进行验证
npx hyperframes check
npx hyperframes snapshot --at <one time per phrase 0.3 s after it lands, plus one mid-exit>
npx hyperframes render -o renders/<name>.mp4
ffprobe -v error -show_entries stream=width,height,r_frame_rate:format=duration -of csv=p=0 renders/<name>.mp4
查看每一张快照和 snapshots/contact-sheet.jpg,并确认:
- 每个短语占据 50–80% 的画面宽度,在手机尺寸下也清晰可读
- 每个短语都有一个主角词,使用强调色或不同的动作
- 任意画面中都没有两个短语重叠
- 所有文本都在对应画幅的安全区域内
- 结尾标识至少停留 1 s,且与开场相呼应
- 尺寸和时长符合要求
最终渲染前进行自我评估
对快照表在以下方面打 1–10 分:吸引力(第一秒)、手机屏幕宽度下的可读性、动态效果质量、构图、准确性,以及“静音测试”(观众能否在没有声音的情况下用一句话概括内容?)。只修改得分为 7 或以下的项目,重新生成快照并重复,直到每项都达到 8 分或以上。如果可以,请安排一次单独的只读评估来打分,避免给自己的作品打分。
| 不要这样做 | 应该这样做 |
|---|---|
| 在两个繁忙布局之间交叉淡化 | 先让旧内容退场,再引入新内容;或者让一个对象贯穿切换 |
| 同时让多个元素运动 | 错开动作,每个时刻只聚焦一个元素 |
| 用漂浮或摇摆来填充停顿 | 保持静止,或让角色做出动作 |
| 为了“活力”添加粒子、彩虹渐变或 3D 翻转 | 添加更有力的事件:揭示、重击或镜头运动 |
| 为了赶时间而加快文本播放 | 删减文字 |
交付
- 海报:选取最有力的定格画面,并将其设为第 0 帧,方便缩略图展示:
ffmpeg -ss <t> -i renders/<name>.mp4 -frames:v 1 poster.jpg,然后运行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 句的配文,可直接发布;描述创意,而不是技术。
- 告知用户短语列表及其时间,并指出修订时应更改哪些短语。
参考这些作品
- @8co28,JIZURA,一款通过节拍点按确定字词时间的歌词文本视频生成器:https://x.com/8co28/status/2102572322016370781
- @deedydas,将一篇文章转化为屏幕上的文字排版:https://x.com/deedydas/status/2104151743907160546
- @stephanlivera,由一行提示词生成、以字体为核心的动态作品集:https://x.com/stephanlivera/status/2103315922098470926
- @deedydas,以有力的标题重击为核心制作的发布视频:https://x.com/deedydas/status/2102787937482252537
- 改编(而非照搬)的创意来自 HyperFrames 动态设计原则和无露脸解说技能、JohnHeibel/ClaudeAnimationBase 的动画指南、howseen-ai/claude-motion-design 的批评循环,以及 latent-spaces/brag。