Remotion 视频创作者
使用Remotion创建程序化视频——即渲染成视频的React组件。本技能编码了Skill Stack的视觉风格和制作解说内容所积累的经验。
目的
构建短格式视频内容(10-60秒),以独特的Risograph风格美学解释Skill Stack概念。视频即代码,因此它们是版本控制的、可复现的,并且可以无限编辑。
何时使用本技能
- 为Skill Stack概念创建解说视频
- 制作动画社交内容(竖屏9:16、正方形1:1、横屏16:9)
- 可视化技术工作流程或渐进式披露
- 任何需要精确时间和排版的视频
不适用于:
- 实况视频剪辑(请使用传统视频工具)
- 复杂3D图形(Remotion专注于2D)
- 一次性快速图形(请使用Figma或Canva)
项目设置
快速开始
# 创建新的Remotion项目
npx create-video@latest my-video
# 安装依赖
cd my-video && npm install
# 添加Google字体
npm install @remotion/google-fonts
# 启动工作室
npm run start # 打开 http://localhost:3000
文件结构
my-video/
├── src/
│ ├── Root.tsx # 组合定义
│ ├── Main.tsx # 主视频组件
│ └── components/ # 可复用元素
├── package.json
├── remotion.config.ts
└── out/ # 渲染后的视频
组合设置 (Root.tsx)
始终为每个视频创建多个宽高比:
import { Composition, Folder } from "remotion";
import { MyVideo } from "./MyVideo";
export const RemotionRoot = () => (
<>
<Folder name="MyVideo">
<Composition
id="MyVideo-Vertical"
component={MyVideo}
durationInFrames={600} // 20秒,30fps
fps={30}
width={1080}
height={1920} // 9:16
/>
<Composition
id="MyVideo-Square"
component={MyVideo}
durationInFrames={600}
fps={30}
width={1080}
height={1080} // 1:1
/>
<Composition
id="MyVideo-Landscape"
component={MyVideo}
durationInFrames={600}
fps={30}
width={1920}
height={1080} // 16:9
/>
</Folder>
</>
);
视觉风格:Skill Stack Risograph
调色板
const C = {
paper: "#FAF8F5", // 暖调米白色背景
coral: "#D4694A", // 强调色、行动号召、高亮
teal: "#1E4D4D", // 标题、选中状态
ink: "#1C1C1C", // 正文文本
muted: "#666666", // 次要文本
dimmed: "#AAAAAA", // 三级文本、禁用状态
white: "#FFFFFF", // 卡片、容器
};
排版
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
import { loadFont as loadPlayfair } from "@remotion/google-fonts/PlayfairDisplay";
import { loadFont as loadJetBrains } from "@remotion/google-fonts/JetBrainsMono";
// 正文文本、UI标签
const { fontFamily: inter } = loadInter("normal", {
weights: ["400", "500", "600"],
subsets: ["latin"],
});
// 标题、引用、情感化文本
const { fontFamily: playfair } = loadPlayfair("normal", {
weights: ["400", "500", "600", "700"],
subsets: ["latin"],
});
// 代码、技术内容、技能名称
const { fontFamily: mono } = loadJetBrains("normal", {
weights: ["400", "500"],
subsets: ["latin"],
});
字号指南
视频中的最小可读字号:
| 元素 | 正方形 (1080) | 竖屏 (1080宽) | 横屏 (1920宽) |
|---|---|---|---|
| 标题 | 64-84px | 64-84px | 72-96px |
| 技能名称 | 36-40px | 36-40px | 40-48px |
| 正文 | 28-32px | 28-32px | 32-36px |
| 标签 | 24-28px | 24-28px | 28-32px |
| 注释 | 22-24px | 22-24px | 24-28px |
关键教训: 总是比你认为的更大。视频会压缩细节。
颗粒叠加
const GrainOverlay: React.FC = () => {
const frame = useCurrentFrame();
return (
<AbsoluteFill
style={{
pointerEvents: "none",
opacity: 0.04,
mixBlendMode: "multiply",
}}
>
<svg width="100%" height="100%">
<defs>
<filter id="grain">
<feTurbulence
type="fractalNoise"
baseFrequency="0.8"
numOctaves="4"
seed={Math.floor(frame / 4)} // 动画颗粒
/>
</filter>
</defs>
<rect width="100%" height="100%" filter="url(#grain)" />
</svg>
</AbsoluteFill>
);
};
动画模式
核心工具
import {
useCurrentFrame,
useVideoConfig,
AbsoluteFill,
interpolate,
spring,
Sequence,
} from "remotion";
淡入/淡出
const fadeIn = interpolate(frame, [0, 30], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fadeOut = interpolate(frame, [150, 180], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// 组合以获取场景不透明度
<div style={{ opacity: fadeIn * fadeOut }}>
弹性动画
const { fps } = useVideoConfig();
// 平滑入场
const entrance = spring({
frame: frame - 20, // 延迟20帧
fps,
config: { damping: 100 }, // 平滑,无弹跳
});
// 弹跳选择
const selectPulse = spring({
frame: frame - 15,
fps,
config: { damping: 12, stiffness: 100 }, // 有弹跳
});
// 应用于变换
<div style={{
opacity: interpolate(entrance, [0, 1], [0, 1]),
transform: `translateY(${interpolate(entrance, [0, 1], [20, 0])}px)`,
}}>
打字机效果
const ScenePrompt: React.FC<{ frame: number }> = ({ frame }) => {
const text = "你的提示词文本在这里";
const charsToShow = Math.floor(frame / 2); // 30fps下每2帧出现1个字符
const visibleText = text.slice(0, charsToShow);
const isTyping = charsToShow < text.length;
const cursorBlink = Math.floor(frame / 18) % 2 === 0;
return (
<div>
"{visibleText}
{(isTyping || cursorBlink) && (
<span style={{
backgroundColor: C.coral,
marginLeft: 4,
opacity: cursorBlink ? 1 : 0,
}}>
{"\u00A0"}
</span>
)}
{!isTyping && '"'}
</div>
);
};
交错列表动画
const ITEMS = ["item-1", "item-2", "item-3"];
{ITEMS.map((item, i) => {
const delay = i * 25; // 每项之间间隔25帧
const s = spring({
frame: Math.max(0, frame - delay - 20),
fps,
config: { damping: 100 },
});
return (
<div
key={item}
style={{
opacity: interpolate(s, [0, 1], [0, 1]),
transform: `translateY(${interpolate(s, [0, 1], [20, 0])}px)`,
}}
>
{item}
</div>
);
})}
场景时间指南
时长计算
- 30 fps = 每秒30帧
- 10秒 = 300帧
- 20秒 = 600帧
节奏经验教训
太快是排在首位的错误。 观众需要时间来:
- 注意到场景变化
- 阅读文本
- 理解含义
- 为下一个变化做准备
最小场景时长:
| 场景类型 | 最小 | 舒适 |
|---|---|---|
| 打字机文本 | 3-4秒 | 5-6秒 |
| 列表出现 | 4-5秒 | 6-8秒 |
| 选择/高亮 | 3-4秒 | 5秒 |
| 技术图表 | 4-5秒 | 6-8秒 |
| 妙语/行动号召 | 2-3秒 | 3-4秒 |
场景重叠模式
场景应该交叉淡入淡出,而不是硬切换:
// 20秒视频的场景时间(600帧)
{frame < 200 && <ScenePrompt frame={frame} />}
{frame >= 160 && frame < 340 && <SceneScan frame={frame - 160} />}
{frame >= 300 && frame < 460 && <SceneSelect frame={frame - 300} />}
{frame >= 420 && frame < 560 && <SceneLoad frame={frame - 420} />}
{frame >= 520 && <ScenePunchline frame={frame - 520} />}
40帧的重叠(1.3秒)与fadeIn/fadeOut插值结合时,可创建平滑的交叉淡入淡出。
工作流程
阶段1:故事板
在编码之前,定义:
- 核心信息 - 观众应该学到的一件事是什么?
- 场景分解 - 4-6个场景,每个场景一个想法
- 总时长 - 社交内容10-20秒,解说30-60秒
- 文本内容 - 每个场景的确切文字
阶段2:搭建框架
- 创建具有适当时长的组合
- 使用占位内容构建场景组件
- 设置基本时间(场景范围)
- 测试所有场景是否出现
阶段3:样式
- 应用调色板和字体
- 添加颗粒叠加
- 确保字号可读
- 在手机(竖屏)和桌面(横屏)上测试
阶段4:动画
- 为每个场景添加fadeIn/fadeOut
- 为入场添加弹性动画
- 微调时间 - 几乎总是需要放慢
- 多次测试完整播放
阶段5:渲染
# 渲染特定组合
npx remotion render MyVideo-Square out/square.mp4
# 渲染所有格式
npx remotion render MyVideo-Vertical out/vertical.mp4
npx remotion render MyVideo-Square out/square.mp4
npx remotion render MyVideo-Landscape out/landscape.mp4
常见错误
❌ 居中问题
问题: 内容出现在左上角。
修复: 使用带有flexbox的AbsoluteFill,而不是position: absolute。
// ✅ 正确
<AbsoluteFill style={{
justifyContent: "center",
alignItems: "center",
}}>
// ❌ 错误
<div style={{ position: "absolute", left: "50%", top: "50%" }}>
❌ 字体太小
问题: 在工作室中可读,在手机上难以辨认。
修复: 正文至少28px,标题64px。
❌ 太快
问题: 文本在消失前无法阅读。
修复: 将初始时长估计翻倍。每个场景至少需要4-8秒。
❌ 硬切换
问题: 场景之间的生硬过渡。
修复: 使用fadeIn/fadeOut使场景重叠30-50帧。
❌ 不支持宽高比
问题: 视频在不同平台上显示错误。
修复: 从一开始就创建所有三种组合(竖屏、正方形、横屏)。
字幕集成
Remotion拥有一流的字幕支持。按成本提供三种选项:
选项1:原生Whisper(推荐 - 免费)
npm install @remotion/captions @remotion/install-whisper-cpp
npx @remotion/install-whisper-cpp --model large-v3-turbo
import { transcribe } from "@remotion/captions";
const result = await transcribe({
inputPath: "/path/to/audio.mp3",
model: "large-v3-turbo",
language: "en",
});
// result.captions = [{ text: "Hello", startTime: 0, endTime: 0.5 }, ...]
选项2:ZapCap API($0.10/分钟)
适用于无需本地模型设置的批量处理。
选项3:Submagic API($0.69/分钟)
预样式化的“病毒式”字幕模板,但价格昂贵。
完整工作流程详情: 参见 references/caption-workflow.md
社区资源
Discord
Remotion Discord - 6000+ 成员。活跃的 #help 和 #showcase 频道。创作者 (Jonny Burger) 直接回复。
需要研究的关键模板
| 模板 | 用例 | 链接 |
|---|---|---|
| TikTok | 竖屏社交视频 | remotion-dev/template-tiktok |
| Audiogram | 播客片段 | remotion-dev/template-audiogram |
| GitHub Unwrapped | 数据驱动视频 | remotion-dev/github-unwrapped |
| claude-remotion-kickstart | AI辅助工作流 | 社区为Claude + Remotion准备的启动器 |
必要的软件包
# 字幕
npm install @remotion/captions @remotion/install-whisper-cpp
# 图形
npm install @remotion/skia # 2D矢量图形
npm install @remotion/three # 使用Three.js的3D
npm install @remotion/lottie # Lottie动画
# 渲染
npm install @remotion/lambda # AWS无服务器渲染
npm install @remotion/player # 嵌入到Web应用中
完整资源列表: 参见 references/community-resources.md
附带的资源
references/what-is-a-skill.tsx- 来自“什么是技能”视频的完整示例组件references/caption-workflow.md- 完整的字幕集成指南 (Whisper, ZapCap, Submagic)references/community-resources.md- 模板、软件包、学习路径
相关技能
- video-caption-creation - 渲染视频的字幕和标签
- youtube-scriptwriting - 用于更长的解说视频脚本
- social-content-creation - 将视频概念重新用于静态帖子
版本历史
-
v1.1 (2026-01-22): 字幕与社区集成
- 通过
@remotion/captions添加了原生Whisper字幕工作流 - 添加了字幕样式预设(极简、粗体、卡拉OK、品牌)
- 添加了社区资源 (Discord, 模板, 软件包)
- 新参考文件:
caption-workflow.md,community-resources.md - 成本比较: Whisper (免费) vs ZapCap ($0.10/分钟) vs Submagic ($0.69/分钟)
- 通过
-
v1.0 (2026-01-21): 初始技能创建
- Risograph视觉风格
- 动画模式 (spring, interpolate, typewriter)
- 时间指南(从“太快”迭代中学习)
- 场景重叠模式
视频即代码。对它们进行版本控制,不断迭代,并构建一个可复用组件的库。