关于 text-animation-copier
text-animation-copier 是一项将视频示例中的文字动画重制为 Remotion React 组件的技能,面向视频创作者。它涵盖逐帧分析、动画参数设计、组件生成及在 Remotion Studio 中测试。
主要功能
- 01提取并检查视频帧
- 02分析文字时序与视觉效果
- 03生成 Remotion React 组件
- 04在 Remotion Studio 注册并测试合成
- 05重制分阶段及逐字动画
适用场景
- 01根据视频片段重制标题动画
- 02按视频时间码匹配文字运动
- 03制作错开或多阶段文字效果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ andchir/ remotion-animations --skill text-animation-copier安装后在 Claude Code 中输入/text-animation-copier确认已加载。
详细介绍
Remotion 文字动画复刻工具
分析视频示例中的文字动画,并将其精确复刻为 Remotion React 组件,包括时间、样式和特效。
快速开始
前置条件
使用此技能前,请确保:
- 已安装 ffmpeg - 提取视频帧所需
- 已设置 Remotion 项目 - 可正常运行的 remotion-animations 仓库
- 视频 URL 或文件路径 - 包含动画视频的直接链接或本地路径
基本工作流
当用户提供包含待复刻文字动画的视频时:
- 下载视频并提取帧(使用
claude-skills/video-viewing) - 检查关键帧,分析动画
- 识别动画属性(时间、特效、位置、样式)
- 生成 Remotion 组件,复刻该动画
- 在 Remotion Studio 中测试并优化动画
详细流程
步骤 1:下载视频并提取帧
使用 video-viewing 技能下载视频并提取帧:
python scripts/download_video.py "VIDEO_URL" -f
此操作会将视频下载到 /mnt/user-data/outputs/,并提取帧以供分析。
替代方案: 如果用户提供的是本地视频文件路径,请直接使用 ffmpeg:
# Extract frames at 1 frame per second
ffmpeg -i path/to/video.mp4 -vf fps=1 /tmp/frames/frame-%04d.png
# Extract frames at specific time interval (e.g., 00:07 to 00:13)
ffmpeg -i path/to/video.mp4 -ss 00:07 -to 00:13 -vf fps=2 /tmp/frames/frame-%04d.png
步骤 2:检查帧并分析动画
用户通常会指定一个时间范围(例如“00:07 - 00:13”)。提取并分析该范围内的帧:
- 找出动画的起始帧和结束帧
- 检查关键转场点(动画行为发生变化的时刻)
- 忽略背景视频——只关注文字元素
- 记录观察结果,包括:
- 文字内容和位置
- 字体、字号、字重、颜色
- 动画类型(淡入、滑动、缩放、旋转、翻转等)
- 时间和缓动曲线
- 多阶段动画(例如,先填充色条,再显示文字)
- 特殊效果(3D 变换、阴影、辉光、粒子)
步骤 3:识别动画属性
根据帧分析结果,记录以下关键属性:
时间配置
- 时长:动画总时长,以秒/帧为单位
- FPS:视频帧率(网络视频通常为 30fps)
- 关键时刻:重要转场对应的帧编号
- 缓动:缓动曲线类型(线性、缓入缓出、三次、贝塞尔)
时间结构示例:
const TIMING = {
PHASE_1_START: 0,
PHASE_1_END: 45,
PHASE_2_START: 48,
PHASE_2_END: 90,
HOLD_END: 180,
};
文字样式
- 字体:Arial、Helvetica、自定义字体
- 字号:像素值
- 字重:normal、bold、300-900
- 颜色:Hex 或 rgba 值
- 字间距:字符之间的额外间距
- 文字对齐:左对齐、居中、右对齐
位置
- 对齐方式:居中、左上、左下等
- 偏移值:距离边缘的像素值
- 响应式定位:使用视口宽度/高度实现居中
动画效果
常见文字动画模式:
1. 淡入/淡出
const opacity = interpolate(
frame,
[START_FRAME, END_FRAME],
[0, 1],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}
);
2. 滑入/滑出
const translateX = interpolate(
frame,
[START_FRAME, END_FRAME],
[-500, 0], // from left to center
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
}
);
3. 缩放动画
const scale = interpolate(
frame,
[START_FRAME, END_FRAME],
[0, 1],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.elastic(1),
}
);
4. 3D 旋转/翻转
const rotateY = interpolate(
frame,
[START_FRAME, END_FRAME],
[0, 360],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.inOut(Easing.ease),
}
);
// Apply with CSS transform
transform: `rotateY(${rotateY}deg)`,
transformStyle: "preserve-3d",
perspective: "1000px"
5. 色条填充动画
const barProgress = interpolate(
frame,
[BAR_START, BAR_END],
[0, 1],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}
);
// Apply to width
width: `${barProgress * 100}%`
6. 交错动画(用于多个字母/单词)
// Each letter starts with a delay
const letterStart = BASE_START + index * STAGGER_DELAY;
const letterEnd = letterStart + ANIMATION_DURATION;
const letterOpacity = interpolate(
frame,
[letterStart, letterEnd],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
7. 弹簧物理效果
const scale = spring({
frame: frame - START_FRAME,
fps: 30,
config: {
damping: 10,
stiffness: 100,
mass: 0.5,
},
});
步骤 4:生成 Remotion 组件
按照以下模式,在 src/animations/text-animation-N/ 中新建 React 组件:
组件结构模板
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
spring,
} from "remotion";
// Animation timing configuration (in frames, at 30fps)
// Reference video time range: HH:MM:SS - HH:MM:SS
const TIMING = {
ANIMATION_START: 0,
ANIMATION_END: 60,
// Add more timing points as needed
};
// Configuration constants
const TEXT = "YOUR TEXT HERE";
const FONT_SIZE = 48;
const FONT_FAMILY = "Arial, sans-serif";
const TEXT_COLOR = "#FFFFFF";
export const TextAnimationN: React.FC = () => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
// Calculate animation values based on current frame
const opacity = interpolate(
frame,
[TIMING.ANIMATION_START, TIMING.ANIMATION_END],
[0, 1],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.inOut(Easing.ease),
}
);
return (
<AbsoluteFill
style={{
backgroundColor: "transparent", // Always transparent for overlays
}}
>
{/* Your animation elements here */}
<div
style={{
fontFamily: FONT_FAMILY,
fontSize: `${FONT_SIZE}px`,
color: TEXT_COLOR,
opacity: opacity,
// Add positioning, transforms, etc.
}}
>
{TEXT}
</div>
</AbsoluteFill>
);
};
重要要求
- 分辨率:始终使用 1920x1080(在 Root.tsx 中配置)
- 透明背景:
backgroundColor: "transparent" - 居中定位:使用
width/2和height/2实现居中 - 精确计时:尽可能贴合原视频的时间
- 代码清晰:添加注释,说明每个动画阶段
步骤 5:注册合成
将新动画添加到 src/Root.tsx:
import { TextAnimationN } from "./animations/text-animation-N/TextAnimationN";
<Composition
id="TextAnimationN"
component={TextAnimationN}
durationInFrames={180} // Adjust based on animation length
fps={30}
width={1920}
height={1080}
defaultProps={{}}
/>
第 6 步:在 Remotion Studio 中测试
npm start
这会在 http://localhost:3000 打开 Remotion Studio,你可以:
- 实时预览动画
- 拖动时间轴浏览各帧,检查时间设置
- 与原始视频进行对比
- 调整数值并立即查看效果
第 7 步:渲染动画
对动画效果满意后:
# Render as WebM with transparency (recommended)
npm run render:text-animation-N
# Or add custom render script to package.json:
npx remotion render src/index.ts TextAnimationN out/text-animation-N.webm --codec vp9 --pixel-format yuva420p
高级技巧
逐字母动画
适用于每个字母独立进行动画的效果:
const Letter: React.FC<{
char: string;
index: number;
totalLetters: number;
}> = ({ char, index, totalLetters }) => {
const frame = useCurrentFrame();
// Stagger each letter's animation
const letterDelay = index * 3; // 3 frames delay per letter
const letterStart = TIMING.ANIMATION_START + letterDelay;
const opacity = interpolate(
frame,
[letterStart, letterStart + 15],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return (
<span style={{ opacity }}>
{char}
</span>
);
};
// In main component:
const letters = TEXT.split("").map((char, i) => (
<Letter key={i} char={char} index={i} totalLetters={TEXT.length} />
));
多阶段动画
适用于包含多个连续阶段的复杂动画:
// Phase 1: Slide in
const slideProgress = interpolate(
frame,
[TIMING.SLIDE_START, TIMING.SLIDE_END],
[0, 1],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
}
);
// Phase 2: Scale pulse (starts after slide completes)
const scaleProgress = interpolate(
frame,
[TIMING.SCALE_START, TIMING.SCALE_END],
[1, 1.2],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.inOut(Easing.ease),
}
);
// Phase 3: Fade out
const fadeOutProgress = interpolate(
frame,
[TIMING.FADEOUT_START, TIMING.FADEOUT_END],
[1, 0],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}
);
// Combine all phases
const translateX = slideProgress * width;
const scale = frame >= TIMING.SCALE_START ? scaleProgress : 1;
const opacity = frame >= TIMING.FADEOUT_START ? fadeOutProgress : 1;
背景效果
适用于带有动画背景的文本(如 TextAnimation2):
// Background appears behind text with 3D rotation
<div
style={{
position: "absolute",
width: charWidth + 20,
height: fontSize + 20,
backgroundColor: BACKGROUND_COLOR,
transform: `rotateY(${bgRotateY}deg) scaleX(${bgScaleX})`,
transformStyle: "preserve-3d",
opacity: bgOpacity,
zIndex: -1, // Behind text
}}
/>
自定义缓动函数
适用于精确匹配时间的场景:
// Built-in easing options:
Easing.linear // Constant speed
Easing.ease // Gentle start and end
Easing.in(Easing.quad) // Accelerate
Easing.out(Easing.quad) // Decelerate
Easing.inOut(Easing.cubic) // Smooth S-curve
Easing.bezier(0.25, 0.1, 0.25, 1) // Custom cubic bezier
// Available easing functions:
// quad, cubic, poly, sin, exp, circle, back, bounce, elastic
技术参考
Remotion 核心 Hook
useCurrentFrame():返回当前帧数useVideoConfig():返回{ fps, width, height, durationInFrames }interpolate(value, inputRange, outputRange, options):平滑映射数值spring(options):基于物理效果的动画Sequence:通过偏移量叠加多个动画
Remotion 必备组件
<AbsoluteFill>:全屏定位容器<Sequence from={frame} durationInFrames={frames}>:时间偏移动画<Audio>:背景音乐/音效<Video>:嵌入视频背景<Img>:静态或动态图片
CSS 变换属性
对于 3D 效果,请使用以下变换属性:
perspective: 1000px; /* Enable 3D space */
transformStyle: preserve-3d; /* Maintain 3D in children */
backfaceVisibility: hidden; /* Hide back of element */
/* Transform functions: */
transform: translateX(px) translateY(px) translateZ(px)
rotateX(deg) rotateY(deg) rotateZ(deg)
scale(factor) scaleX() scaleY()
skewX(deg) skewY(deg);
常见动画模式
1. 文字下三分之一(类似 TextAnimation1)
- 彩色条从左向右填充
- 彩色条完成后显示文字
- 放置在角落(通常是左下角)
2. 带 3D 翻转的飞入文字(类似 TextAnimation2)
- 文字从屏幕外飞入
- 每个字母在移动时进行 3D 翻转
- 背景方框旋转到位,显示在字母后方
- 采用错开的时间安排,增强视觉效果
3. 打字机效果
const visibleChars = Math.floor(
interpolate(
frame,
[START, END],
[0, TEXT.length],
{ extrapolateRight: "clamp" }
)
);
return <div>{TEXT.substring(0, visibleChars)}</div>;
4. 弹跳进入
const bounceScale = spring({
frame: frame - START_FRAME,
fps: 30,
config: {
damping: 12,
stiffness: 200,
mass: 0.5,
},
});
5. 故障效果
const glitchOffset = Math.random() * 10 - 5;
const glitchActive = frame % 10 === 0; // Every 10 frames
return (
<div style={{
transform: glitchActive
? `translate(${glitchOffset}px, ${glitchOffset}px)`
: 'none',
}}>
{TEXT}
</div>
);
最佳实践
1. 精确控制时间
- 将时间转换为帧数:30fps 时,1 秒 = 30 帧
- 匹配原始时间:使用视频时间码设置 TIMING 常量
- 考虑延迟:错开动画,营造流畅的视觉节奏
2. 性能
- 避免逐帧进行复杂计算:尽可能提前计算
- 优先使用 interpolate,而非手动计算:它经过优化,能够处理边界情况
- 尽量减少重新渲染:保持组件逻辑简单
3. 代码组织
- 将常量放在顶部:把 TIMING、COLORS、SIZES 集中在一处
- 为各阶段添加注释:说明每个动画阶段的作用
- 使用可复用组件:提取重复模式(如 Letter 组件)
4. 视觉还原度
- 尽可能匹配字体:使用 Web 安全字体或加载自定义字体
- 确保颜色准确:使用视频帧中的颜色选取器
- 精确定位:测量视频帧中的距离
5. 测试工作流
- 在 Remotion Studio 中测试:立即获得视觉反馈
- 并排对比:播放原始视频,同时查看动画
- 检查所有帧:拖动时间轴浏览,确认过渡流畅
- 渲染并检查:最终渲染效果可能与预览不同
故障排除
动画时间不匹配
- 确认 FPS 与原始视频一致(通常为 30fps)
- 检查 TIMING 常量是否准确
- 使用
console.log(frame)调试帧数值
文字位置不正确
- 再次检查
useVideoConfig()返回的width和height - 确认居中计算:
width/2 - textWidth/2 - 在 Remotion Studio 中测试不同的视口尺寸
动画卡顿
- 增加合成中的
durationInFrames - 检查缓动函数是否平滑(而非线性)
- 确认帧率稳定(30fps)
3D 变换无效
- 在父元素上添加
perspective: "1000px" - 添加
transformStyle: "preserve-3d" - 检查浏览器是否支持 3D 变换
文本未显示
- 确认
opacity不为 0 - 检查
zIndex层叠顺序 - 确保颜色与背景形成对比
- 确认帧范围包含文本出现的时间
示例工作流
示例 1:文本淡入
用户提供:文本从 00:03 到 00:05 淡入的视频
- 下载视频并提取帧
- 分析帧:文本 "HELLO" 的不透明度从 0% 渐变到 100%
- 计算帧数:3 秒到 5 秒 = 第 90 帧到第 150 帧(30fps)
- 创建带淡入插值的组件
- 测试并渲染
示例 2:多行文本交错出现
用户提供:3 行文本从 00:10 到 00:15 依次出现的视频
- 提取 10-15 秒的帧
- 确定:第 1 行在 10 秒出现,第 2 行在 12 秒出现,第 3 行在 14 秒出现
- 计算每行的时间(30fps)
- 创建包含 3 个文本元素的组件,并为每个元素设置不同的时间偏移
- 测试交错延迟是否与原视频一致
- 渲染
示例 3:复杂的多阶段动画
用户提供:文本从 00:20 到 00:30 滑入、旋转,然后滑出的视频
- 每隔 0.5 秒提取一帧
- 确定各个阶段:
- 阶段 1(20-23 秒):从左侧滑入
- 阶段 2(23-26 秒):旋转 360 度
- 阶段 3(26-30 秒):向右滑出
- 为每个阶段创建 TIMING 常量
- 实现 3 个独立的插值
- 在 style 对象中组合变换
- 测试各阶段之间是否平滑过渡
- 渲染并比较
与其他技能集成
此技能与以下内容结合使用效果最佳:
claude-skills/video-viewing:用于下载视频和提取帧remotion-best-practices:用于掌握高级 Remotion 技术- 视频编辑知识:了解时间、缓动和关键帧
输出检查清单
交付文本动画复刻时,请确保:
- 在
src/animations/text-animation-N/中创建组件 - 在
src/Root.tsx中注册 - 分辨率为 1920x1080
- 背景透明
- 文本居中(如有指定)
- 时间与原视频一致
- 为 TIMING 常量添加注释说明
- 代码简洁且注释完善
- 在 Remotion Studio 中测试
- 在 package.json 中添加渲染脚本(可选)
- 在注释中注明原视频的时间范围
命令速查
# Download video with frame extraction
python scripts/download_video.py "VIDEO_URL" -f
# Start Remotion Studio for testing
npm start
# Type check the code
npm run typecheck
# Render animation
npx remotion render src/index.ts TextAnimationN out/output.webm --codec vp9 --pixel-format yuva420p
# Extract specific time range from video
ffmpeg -i input.mp4 -ss 00:07 -to 00:13 -vf fps=2 output-%04d.png
实用资源
- Remotion 文档:https://www.remotion.dev/docs
- Remotion 缓动可视化工具:https://www.remotion.dev/docs/easing
- 三次贝塞尔曲线生成器:https://cubic-bezier.com/
- CSS Transform 参考:https://developer.mozilla.org/en-US/docs/Web/CSS/transform
总结
此技能帮助你系统地分析视频中的文本动画,并将其复刻为可复用的 Remotion 组件。关键在于仔细观察、精确转换时间,并充分利用 Remotion 强大的插值和动画功能。
始终优先匹配原动画的时间和视觉感受,而非追求像素级复刻。缓动或时间上的细微变化可以让动画感觉更自然,同时仍能保留原动画的精髓。