mcpField
K
kinetic-typography

kinetic-typography

github.com/phantomtonyt创意与媒体2151 次安装3 周前更新
代码仓库

关于 kinetic-typography

Kinetic Typography 是用于制作以文字为画面主体的节奏型视频技能,适合将短语和重点词转化为动态视觉的创作者。它围绕节拍、字词动作和强对比版式规划动画。

主要功能

  1. 01短语与重点词拆分
  2. 02按语音或音乐节奏排时
  3. 03文字弹入、滑动、翻转等动作
  4. 04高对比色彩与字体指导
  5. 05循环结尾与画面检查

适用场景

  1. 01制作节奏鲜明的标题短片
  2. 02将旁白或歌词转为动态文字
  3. 03为社交视频制作片尾标题版式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/phantomtonyt/motion-design-skills --skill kinetic-typography

安装后在 Claude Code 中输入/kinetic-typography确认已加载。

详细介绍

动态排版

制作让文字本身成为画面的影片。一句话落下、填满画面、受到冲击,随后随着节奏被下一句话替换。整体应当大胆、迅速且富有音乐感,就像一张会动的海报。

选项

选项默认值备注
formatvertical 1080×1920或横屏 1920×1080、方形 1080×1080
duration10–20 s与旁白或歌曲片段时长匹配
tonepunchycalm(幻灯片式切换和 0.4 s 淡入淡出,翻转更少)、punchy(默认)、chaotic(全大写、猛然出现、抖动,每隔一句就翻转一次)
rhythmfixed tempo或旁白词语时间点,或音乐节拍

简述(规划前回答)

  1. 观众应该记住的那句话是什么?
  2. 将脚本拆分为每段 1–4 个词;每段标出一个主打词。
  3. 能量在哪一刻达到顶峰(触发颜色翻转的那句话)?
  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 的静止节拍。

风格规则(始终遵守)

这些规则构成了这种风格;无论颜色或文字如何变化,都应遵守。

  1. 字体宽度占画面宽度的 50–80%,因为小字看起来像字幕,而不是视觉主体。
  2. 屏幕上一次只呈现一个想法:1–4 个词,最多 3 行。其他内容都应离开画面。
  3. 每个词出现时都要伴随动作(猛然出现、滑入、拉伸、遮罩揭示、翻转)。不要单纯淡入。
  4. 切换要落在节奏上:每隔 0.25–0.7 s 出现一个新词或动作,关键词要落在节拍或重读音节上。
  5. 对比度要强烈:近黑色搭配米白色,或反过来,再加一种仅用于关键词的强调色。
  6. 每段都要有一个主打词:尺寸最大、使用强调色,或以不同方式动效呈现。

默认值(可自由更改)

字体、颜色、文字、时间安排和背景都可以更改。如果用户提供了品牌字体或颜色,以用户提供的为准。优先级:用户的品牌规范或规格要求,其次是明确请求,最后是这些默认值。

  • 背景 #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 替换为下面的模板。

构建前先确定

写代码前,先用一行向用户确认以下内容。

  1. **画幅:**9:16(1080×1920)或 16:9(1920×1080)。默认采用 9:16。
  2. **文案与时长:**将脚本拆分为每段 1–4 个词。有配音时,根据配音的词语时间戳确定时长(npx hyperframes transcribe voice.wav)。没有配音时,每段时长设为 0.6–1.4 秒,重点词语的时长要更长。
  3. **节奏来源:**配音(跟随重读音节)、音乐(跟随节拍;npx hyperframes beats music.mp3)或固定速度(例如 120 BPM = 每 0.5 秒一个节拍)。
  4. 每段的重点词语以及对应的动效。
需求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 句的配文,可直接发布;描述创意,而不是技术。
  • 告知用户短语列表及其时间,并指出修订时应更改哪些短语。

参考这些作品

评论

支持 Markdown