kinetic-typography
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypography in Motion
动态文字排版
Animate text expressively: splitting copy into lines/words/characters, staggering reveals, masking, blurring, morphing weight, and running text along a path. Produces runnable code for the web (CSS, GSAP, Framer Motion) and video (Remotion), plus an After Effects expression approach.
实现富有表现力的文字动画:将文本拆分为行/单词/字符、错开展示、遮罩、模糊、字体粗细变形,以及文字沿路径排列。提供可直接运行的Web端代码(CSS、GSAP、Framer Motion)和视频端代码(Remotion),还有After Effects表达式实现方案。
When to use
使用场景
- Headline / tagline entrances and animated title cards.
- Lyric videos, caption/subtitle reveals, kinetic-typography pieces.
- Variable-font animation (weight, width, slant, optical size).
- Text-on-path, text stroke draw-on, and per-character morph effects.
- 标题/标语入场动画及动画标题卡片。
- 歌词视频、字幕/副标题展示、动态排版作品。
- 可变字体动画(粗细、宽度、倾斜、光学尺寸)。
- 文字沿路径排列、文字描边绘制、逐字符变形效果。
Core workflow
核心工作流程
1. Get static typography right first
1. 先搞定静态文字排版
Motion cannot rescue bad type. Before animating, lock line-height (1.1-1.2 for display headlines, 1.4-1.6 for body), tracking (tighten display by -1% to -3%, e.g. ), alignment, and weight. Then split and animate.
letter-spacing: -0.02em动效无法拯救糟糕的文字排版。在制作动画前,先锁定行高(展示类标题为1.1-1.2,正文为1.4-1.6)、字距(展示类文字收紧-1%至-3%,例如 )、对齐方式和字体粗细。之后再拆分并制作动画。
letter-spacing: -0.02em2. Choose split granularity
2. 选择拆分粒度
- By line — calmest, most premium; best for multi-line paragraphs and elegant headlines.
- By word — energetic, good for taglines and lyric hits.
- By character — most kinetic/playful; risk of feeling busy on long copy. Reserve for short strings.
Tools: or GSAP on the web; / plus in Remotion; staggered children in Framer Motion.
SplitTypeSplitText.split('').split(' ')interpolatemotionCritical accessibility note: splitting into per-character spans destroys the text for screen readers and breaks copy-paste. Always set on the container with the full string and on the split fragments.
aria-labelaria-hidden="true"- 按行拆分 — 最沉稳、高端;适用于多行段落和优雅的标题。
- 按单词拆分 — 富有活力,适合标语和歌词重点部分。
- 按字符拆分 — 最具动感/趣味性;长文本使用可能显得杂乱。仅用于短文本字符串。
工具:Web端使用或GSAP ;Remotion中使用 / 搭配;Framer Motion中使用组件的错开子元素功能。
SplitTypeSplitText.split('').split(' ')interpolatemotion重要无障碍提示:将文本拆分为单个字符的span标签会破坏屏幕阅读器的文本识别,并导致无法复制粘贴。务必在容器上设置属性,填入完整文本内容,并在拆分后的片段上设置。
aria-labelaria-hidden="true"3. Apply a reveal technique
3. 应用展示技巧
The four workhorse reveals:
- Mask / clip reveal (most robust) — wrap each line in ; animate the child from
overflow: hiddentotranslateY(100%). The text wipes up from a hidden baseline. No blur artifacts, GPU-cheap, the industry-standard headline reveal.0 - Blur-in — animate with
filter: blur(12px) -> blur(0). Soft, premium, "focus-pull" feel. Costlier to render; keep blur radius modest and prefer short durations.opacity 0 -> 1 - Clip-path wipe — animate to
clip-path: inset(0 100% 0 0)for a directional reveal without moving the glyphs.inset(0 0 0 0) - Character stagger — split to chars, fade/translate each with a 20-40ms offset.
四种常用的展示方式:
- 遮罩/裁剪展示(最稳定) — 每行文字包裹在的容器中;将子元素从
overflow: hidden动画到translateY(100%)。文字从隐藏的基线向上滑入。无模糊 artifacts,GPU负载低,是行业标准的标题展示动画。0 - 模糊淡入 — 动画搭配
filter: blur(12px) -> blur(0)。柔和、高端,有“聚焦”的感觉。渲染成本较高;模糊半径适中,优先选择短时长。opacity 0 -> 1 - 裁剪路径擦除 — 动画到
clip-path: inset(0 100% 0 0),实现文字不移动的定向展示效果。inset(0 0 0 0) - 字符错开 — 拆分为字符,每个字符以20-40ms的偏移量进行淡入/位移动画。
4. Apply principle-correct timing
4. 应用符合原则的时序
Inline essentials (so this skill stands alone):
- Enter with ease-out: (easeOutExpo) feels premium.
cubic-bezier(0.16, 1, 0.3, 1) - Per-fragment duration 400-600ms.
- Stagger: lines 60-100ms, words 40-70ms, characters 20-40ms apart.
- Cap total reveal near ~800ms; for long strings, reduce per-char offset or switch to word/line granularity.
核心要点(确保此技能可独立使用):
- 入场动画使用ease-out曲线:(easeOutExpo),质感高端。
cubic-bezier(0.16, 1, 0.3, 1) - 每个片段的时长为400-600ms。
- 错开时间:行60-100ms,单词40-70ms,字符20-40ms。
- 总展示时长控制在约800ms以内;对于长文本,减少字符偏移量或切换为按单词/行拆分的粒度。
5. Mask-reveal, copy-paste (vanilla CSS + JS line split)
5. 遮罩展示示例,可直接复制粘贴(原生CSS + JS按行拆分)
html
<h1 class="reveal" aria-label="Designed for motion">Designed for motion</h1>css
.reveal .line { overflow: hidden; }
.reveal .line > span {
display: inline-block;
transform: translateY(110%);
animation: rise 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.reveal .line:nth-child(2) > span { animation-delay: 0.08s; }
.reveal .line:nth-child(3) > span { animation-delay: 0.16s; }
@keyframes rise { to { transform: translateY(0); } }html
<h1 class="reveal" aria-label="Designed for motion">Designed for motion</h1>css
.reveal .line { overflow: hidden; }
.reveal .line > span {
display: inline-block;
transform: translateY(110%);
animation: rise 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.reveal .line:nth-child(2) > span { animation-delay: 0.08s; }
.reveal .line:nth-child(3) > span { animation-delay: 0.16s; }
@keyframes rise { to { transform: translateY(0); } }6. Variable-font weight animation
6. 可变字体粗细动画
css
@keyframes thicken {
from { font-variation-settings: "wght" 200; }
to { font-variation-settings: "wght" 800; }
}
.headline {
font-family: "Inter var", sans-serif;
animation: thicken 0.8s cubic-bezier(0.65, 0, 0.35, 1) forwards;
}Animate (covers any axis: , , , , plus custom axes), not , to hit non-standard values and to interpolate smoothly. Confirm the loaded font is actually a variable font and the axis range (e.g. wght 100-900) before animating, or the value clamps silently.
font-variation-settingswghtwdthslntopszfont-weightcss
@keyframes thicken {
from { font-variation-settings: "wght" 200; }
to { font-variation-settings: "wght" 800; }
}
.headline {
font-family: "Inter var", sans-serif;
animation: thicken 0.8s cubic-bezier(0.65, 0, 0.35, 1) forwards;
}应动画(涵盖所有轴:、、、以及自定义轴),而非,这样可以实现非标准值的过渡和平滑插值。动画前请确认加载的字体确实是可变字体,且轴范围正确(例如wght 100-900),否则值会被静默截断。
font-variation-settingswghtwdthslntopszfont-weightDeliver & verify (standalone HTML)
交付与验证(独立HTML文件)
Packaged helper ():scripts/freezes thescripts/seek-shot.sh anim.html 0 1.5 3harness and screenshots each moment;?t=Ntiles them for one-glance review. Seescripts/contact-sheet.sh sheet.png frame-*.png.scripts/README.md
For a web text reveal / kinetic-title deliverable, ship one file that opens directly in a browser — no build step. One file is the right tier for a headline, title card, or single reveal.
.htmlOutput contract:
- One file: markup with
.htmlon the container, the reveal in inlinearia-label(or one inline GSAP<style>from CDN).<script> - One animation driver — a CSS reveal, or a single GSAP timeline; not both.
@keyframes - Include the freeze harness below so any moment can be screenshotted deterministically.
- Split AFTER so line breaks are correct in the frozen frame.
document.fonts.ready
Freeze harness — pin a frame for screenshots.
html
<script>
const t = new URLSearchParams(location.search).get("t");
if (t !== null) {
// CSS @keyframes reveal:
document.querySelectorAll(".reveal *").forEach(el => {
el.style.animationDelay = (-parseFloat(t)) + "s";
el.style.animationPlayState = "paused";
});
// GSAP timeline instead? → tl.pause(); tl.seek(parseFloat(t));
}
window.__ready = true; // ready signal for headless wait
</script>Verify loop — render → freeze → screenshot → check:
- Open the file frozen at start / mid / end: ,
…/type.html?t=0,?t=<dur/2>.?t=<dur> - Screenshot each frozen frame.
- Check fidelity (matches the brief) and artifacts — clipped glyphs/descenders, masks that don't fully hide the baseline, FOUC or wrong line breaks before fonts load, blur fringing.
bash
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/type.html?t=0.4" frame-mid.pngBefore you finish:
- Opens standalone in a browser — no console errors, no missing CDN/font.
- One reveal driver; freezes the exact frame correctly.
?t=N - Screenshotted at start / mid / end — no clipped glyphs, no FOUC, line breaks correct.
- honored (text shown in final state, no motion).
prefers-reduced-motion - on the container; split fragments
aria-label— copy and screen-reader text intact.aria-hidden
打包助手脚本(目录):scripts/会冻结scripts/seek-shot.sh anim.html 0 1.5 3控制的动画,并截取每个时刻的截图;?t=N将截图拼接成一张预览图,方便快速查看。详情见scripts/contact-sheet.sh sheet.png frame-*.png。scripts/README.md
对于Web端文本展示/动态标题的交付成果,需提供可直接在浏览器中打开的单个文件 — 无需构建步骤。单个文件是标题、标题卡片或单一展示效果的理想交付形式。
.html输出规范:
- 单个文件:容器上带有
.html的标记,内嵌aria-label中的展示动画(或从CDN引入的单个GSAP<style>)。<script> - 仅使用一种动画驱动方式 — CSS 展示动画,或单个GSAP时间轴;不要同时使用两种。
@keyframes - 包含下方的冻结工具,以便可以确定性地截取任意时刻的截图。
- 在之后再拆分文本,确保冻结帧中的换行正确。
document.fonts.ready
冻结工具 — 固定帧用于截图
html
<script>
const t = new URLSearchParams(location.search).get("t");
if (t !== null) {
// CSS @keyframes 展示动画:
document.querySelectorAll(".reveal *").forEach(el => {
el.style.animationDelay = (-parseFloat(t)) + "s";
el.style.animationPlayState = "paused";
});
// 如果是GSAP时间轴?→ tl.pause(); tl.seek(parseFloat(t));
}
window.__ready = true; // 无头模式等待的就绪信号
</script>验证流程 — 渲染 → 冻结 → 截图 → 检查:
- 打开文件并冻结在开始/中间/结束时刻:、
…/type.html?t=0、?t=<dur/2>。?t=<dur> - 截取每个冻结帧的截图。
- 检查保真度(符合需求)和瑕疵 — 文字被裁剪/下行部分被遮挡、遮罩未完全隐藏基线、字体加载前出现FOUC(无样式内容闪烁)或错误换行、模糊边缘。
bash
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/type.html?t=0.4" frame-mid.png完成前检查:
- 可在浏览器中独立打开 — 无控制台错误,无缺失的CDN资源/字体。
- 仅使用一种展示驱动方式;可正确冻结指定帧。
?t=N - 已截取开始/中间/结束时刻的截图 — 无文字裁剪、无FOUC、换行正确。
- 尊重设置(文本显示为最终状态,无动画)。
prefers-reduced-motion - 容器上带有;拆分后的片段设置了
aria-label— 可复制文本,且屏幕阅读器可识别完整文本。aria-hidden
Quick reference
快速参考
| Effect | Core CSS / approach |
|---|---|
| Mask up reveal | parent |
| Blur in | |
| Directional wipe | |
| Char stagger | split chars, 20-40ms offset, fade+rise |
| Variable weight | animate |
| Text on path | SVG |
| Stroke draw-on | SVG text as path, animate |
| 效果 | 核心CSS/实现方式 |
|---|---|
| 遮罩向上展示 | 父元素 |
| 模糊淡入 | |
| 定向擦除 | |
| 字符错开 | 拆分为字符,20-40ms偏移量,淡入+上移 |
| 可变字体粗细 | 动画 |
| 文字沿路径排列 | SVG |
| 描边绘制 | 将SVG文字转为路径,动画 |
Text on a path (SVG)
文字沿路径排列(SVG)
html
<svg viewBox="0 0 600 200">
<path id="curve" d="M20,150 Q300,20 580,150" fill="none" />
<text font-size="42" fill="#fff">
<textPath href="#curve" startOffset="0%">Follow the curve</textPath>
</text>
</svg>Animate the attribute ( -> ) to scroll text along the path, or animate the path to make text flow on a morphing curve.
startOffset0%100%dhtml
<svg viewBox="0 0 600 200">
<path id="curve" d="M20,150 Q300,20 580,150" fill="none" />
<text font-size="42" fill="#fff">
<textPath href="#curve" startOffset="0%">Follow the curve</textPath>
</text>
</svg>可通过动画属性( -> )实现文字沿路径滚动,或动画路径的属性,让文字在变形的曲线上流动。
startOffset0%100%dReference files
参考文件
- — copy-paste reveal cookbook: mask/blur/clip/char-stagger in CSS, GSAP SplitText timelines, Framer Motion staggered variants, Remotion per-character interpolation, variable-font keyframes, and a screen-reader-safe split helper.
references/reveal-recipes.md
- — 可直接复制的展示动画手册:CSS中的遮罩/模糊/裁剪/字符错开效果、GSAP SplitText时间轴、Framer Motion错开变体、Remotion逐字符插值、可变字体关键帧,以及屏幕阅读器友好的拆分工具。
references/reveal-recipes.md