12d - 实战案例集
本文是《AI Agent 实战手册》第 12 章第 4 节。 上一节:12c-端到端工作流 | 下一节:12e-成本与生产部署
概述
理论和工作流讲完了,现在上真刀真枪。本节提供三个完整的 Remotion + AI Agent 实战案例——产品演示视频、编程教程视频、社交媒体短视频——每个案例都包含完整的自然语言 prompt 输入和 AI 生成的 React/TypeScript 代码输出,你可以直接复制到项目中运行。这些案例覆盖了最常见的代码驱动视频场景,帮你快速建立从 prompt 到成品视频的实操经验。
1. 案例总览与工具准备
工具推荐
| 工具 | 用途 | 价格 | 适用场景 |
|---|---|---|---|
| Remotion v4.x | React 视频框架 | 个人及 ≤3 人公司免费;企业需 Company License | 所有案例的核心渲染引擎 |
| Claude Code + Remotion Agent Skills | AI 代码生成 | Claude Pro $20/月;Max $100-200/月 | 从 prompt 生成视频代码 |
| Remotion Studio | 本地实时预览 | 免费(含在 Remotion 中) | 开发调试阶段 |
| Google Fonts / @remotion/google-fonts | 字体加载 | 免费 | 视频中的文字排版 |
| Remotion Lambda | AWS 云渲染 | 框架免费(AWS 费用约 $0.01-0.05/视频) | 批量渲染和生产部署 |
| FFmpeg | 视频后处理 | 免费(开源) | 格式转换、压缩 |
| Unsplash / Pexels API | 免版权素材 | 免费 | 案例中的背景图片 |
| ElevenLabs / OpenAI TTS | AI 语音旁白 | ElevenLabs 免费层 10k 字符/月;付费 $5/月起 | 教程视频配音 |
操作步骤
步骤 1:确认环境就绪
确保你已完成 12b-环境搭建 中的所有步骤:
# 确认 Remotion 项目已初始化
npx remotion studio # 应能打开 Studio 预览界面
# 确认 Claude Code + Remotion Skills 已安装
# 在 Claude Code 中输入:
/skills
# 应能看到 remotion-dev/skills 已激活步骤 2:理解案例结构
每个案例遵循统一结构:
┌─────────────────────────────────────────────────────┐
│ 1. 场景描述 — 这个视频要解决什么问题 │
│ 2. Prompt 输入 — 给 Claude Code 的自然语言指令 │
│ 3. 代码输出 — AI 生成的完整 React/TypeScript 代码 │
│ 4. 渲染命令 — 如何预览和导出最终视频 │
│ 5. 定制指南 — 如何修改参数适配你的需求 │
└─────────────────────────────────────────────────────┘2. 案例一:SaaS 产品功能演示视频
场景描述
你是一个 SaaS 产品的独立开发者,需要为新上线的「智能仪表盘」功能制作一段 30 秒的产品演示视频,用于官网 landing page 和 Product Hunt 发布。视频需要展示:核心功能亮点、数据可视化动画、CTA(行动号召)。
Prompt 输入
在 Claude Code 中输入以下 prompt:
帮我用 Remotion 创建一个 SaaS 产品演示视频,要求:
1. 视频参数:1920x1080,30fps,时长 30 秒(900 帧)
2. 产品名称:DataPulse Analytics
3. 视频结构(5 个场景):
- 场景 1(0-5秒):品牌开场 — 产品 logo 和 tagline "实时数据,智能决策" 带弹性动画入场
- 场景 2(5-12秒):痛点展示 — 左侧显示杂乱的电子表格图标,右侧显示 "数据混乱?决策迟缓?" 文字
- 场景 3(12-20秒):功能展示 — 模拟仪表盘界面,包含一个动态增长的柱状图和一个环形进度图
- 场景 4(20-26秒):数据亮点 — 三个关键指标卡片依次飞入:"效率提升 3x"、"决策速度 +85%"、"数据准确率 99.9%"
- 场景 5(26-30秒):CTA 结尾 — "立即免费试用" 按钮带脉冲动画,下方显示网址
4. 设计风格:深色主题(#0F172A 背景),渐变强调色(#3B82F6 到 #8B5CF6),现代科技感
5. 动画要求:所有元素使用 spring() 动画,场景切换使用淡入淡出
6. 将所有场景组件放在独立文件中,主 Composition 文件负责编排代码输出
以下是 AI 生成的完整代码(经过整理和注释):
文件:src/ProductDemo/index.tsx(主 Composition)
import React from "react";
import { AbsoluteFill, Sequence, useCurrentFrame, interpolate } from "remotion";
import { BrandIntro } from "./BrandIntro";
import { PainPoints } from "./PainPoints";
import { DashboardShowcase } from "./DashboardShowcase";
import { MetricsHighlight } from "./MetricsHighlight";
import { CallToAction } from "./CallToAction";
// 视频参数:1920x1080, 30fps, 30秒 = 900帧
export const ProductDemo: React.FC = () => {
const frame = useCurrentFrame();
return (
<AbsoluteFill
style={{
backgroundColor: "#0F172A",
fontFamily: "Inter, sans-serif",
}}
>
{/* 场景 1:品牌开场(0-5秒,0-150帧) */}
<Sequence durationInFrames={150}>
<BrandIntro />
</Sequence>
{/* 场景 2:痛点展示(5-12秒,150-360帧) */}
<Sequence from={150} durationInFrames={210}>
<PainPoints />
</Sequence>
{/* 场景 3:功能展示(12-20秒,360-600帧) */}
<Sequence from={360} durationInFrames={240}>
<DashboardShowcase />
</Sequence>
{/* 场景 4:数据亮点(20-26秒,600-780帧) */}
<Sequence from={600} durationInFrames={180}>
<MetricsHighlight />
</Sequence>
{/* 场景 5:CTA 结尾(26-30秒,780-900帧) */}
<Sequence from={780} durationInFrames={120}>
<CallToAction />
</Sequence>
{/* 全局场景切换遮罩 */}
{[150, 360, 600, 780].map((transitionFrame) => {
const opacity = interpolate(
frame,
[transitionFrame - 10, transitionFrame, transitionFrame + 10],
[0, 0.6, 0],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return (
<AbsoluteFill
key={transitionFrame}
style={{ backgroundColor: "#0F172A", opacity }}
/>
);
})}
</AbsoluteFill>
);
};文件:src/ProductDemo/BrandIntro.tsx(场景 1:品牌开场)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
export const BrandIntro: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Logo 弹性缩放动画
const logoScale = spring({
frame,
fps,
config: { damping: 12, stiffness: 200, mass: 0.8 },
});
// Tagline 延迟淡入 + 上移
const taglineProgress = spring({
frame: frame - 20,
fps,
config: { damping: 15, stiffness: 100 },
});
const taglineY = interpolate(taglineProgress, [0, 1], [30, 0]);
const taglineOpacity = interpolate(taglineProgress, [0, 1], [0, 1]);
// 退场淡出(最后 15 帧)
const exitOpacity = interpolate(frame, [135, 150], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
opacity: exitOpacity,
}}
>
{/* 背景渐变光晕 */}
<div
style={{
position: "absolute",
width: 600,
height: 600,
borderRadius: "50%",
background:
"radial-gradient(circle, rgba(59,130,246,0.15) 0%, transparent 70%)",
transform: `scale(${logoScale})`,
}}
/>
{/* 产品名称 */}
<div
style={{
transform: `scale(${logoScale})`,
fontSize: 72,
fontWeight: 800,
background: "linear-gradient(135deg, #3B82F6, #8B5CF6)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
letterSpacing: -2,
}}
>
DataPulse Analytics
</div>
{/* Tagline */}
<div
style={{
marginTop: 20,
fontSize: 28,
color: "#94A3B8",
transform: `translateY(${taglineY}px)`,
opacity: taglineOpacity,
letterSpacing: 4,
}}
>
实时数据,智能决策
</div>
</AbsoluteFill>
);
};文件:src/ProductDemo/DashboardShowcase.tsx(场景 3:仪表盘功能展示)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
// 动态柱状图组件
const BarChart: React.FC<{ progress: number }> = ({ progress }) => {
const bars = [
{ label: "Q1", height: 60, color: "#3B82F6" },
{ label: "Q2", height: 80, color: "#6366F1" },
{ label: "Q3", height: 45, color: "#8B5CF6" },
{ label: "Q4", height: 95, color: "#A855F7" },
];
return (
<div style={{ display: "flex", alignItems: "flex-end", gap: 24, height: 200 }}>
{bars.map((bar, i) => (
<div key={bar.label} style={{ textAlign: "center" }}>
<div
style={{
width: 60,
height: bar.height * 2 * Math.min(progress * 1.5 - i * 0.2, 1),
background: `linear-gradient(180deg, ${bar.color}, ${bar.color}88)`,
borderRadius: "8px 8px 0 0",
transition: "height 0.1s",
}}
/>
<div style={{ color: "#94A3B8", fontSize: 14, marginTop: 8 }}>
{bar.label}
</div>
</div>
))}
</div>
);
};
// 环形进度图组件
const DonutChart: React.FC<{ progress: number }> = ({ progress }) => {
const radius = 70;
const circumference = 2 * Math.PI * radius;
const percentage = 78;
const strokeDashoffset =
circumference - (circumference * percentage * progress) / 100;
return (
<svg width={200} height={200} viewBox="0 0 200 200">
{/* 背景环 */}
<circle
cx={100} cy={100} r={radius}
fill="none" stroke="#1E293B" strokeWidth={14}
/>
{/* 进度环 */}
<circle
cx={100} cy={100} r={radius}
fill="none" stroke="url(#gradient)" strokeWidth={14}
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
transform="rotate(-90 100 100)"
/>
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#3B82F6" />
<stop offset="100%" stopColor="#8B5CF6" />
</linearGradient>
</defs>
{/* 中心文字 */}
<text
x={100} y={105}
textAnchor="middle" fill="white"
fontSize={32} fontWeight={700}
>
{Math.round(percentage * progress)}%
</text>
</svg>
);
};
export const DashboardShowcase: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const enterProgress = spring({
frame,
fps,
config: { damping: 14, stiffness: 80 },
});
const chartProgress = interpolate(frame, [30, 150], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const panelY = interpolate(enterProgress, [0, 1], [60, 0]);
return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
{/* 模拟仪表盘面板 */}
<div
style={{
width: 1200,
padding: 48,
background: "linear-gradient(135deg, #1E293B, #0F172A)",
borderRadius: 24,
border: "1px solid #334155",
transform: `translateY(${panelY}px)`,
opacity: enterProgress,
boxShadow: "0 25px 50px rgba(0,0,0,0.5)",
}}
>
{/* 标题栏 */}
<div style={{
fontSize: 24, fontWeight: 600, color: "white",
marginBottom: 40, display: "flex", alignItems: "center", gap: 12,
}}>
<div style={{
width: 12, height: 12, borderRadius: "50%",
background: "#22C55E",
}} />
实时业务仪表盘
</div>
{/* 图表区域 */}
<div style={{ display: "flex", justifyContent: "space-around", alignItems: "center" }}>
<div style={{ textAlign: "center" }}>
<BarChart progress={chartProgress} />
<div style={{ color: "#64748B", marginTop: 16, fontSize: 16 }}>
季度营收趋势
</div>
</div>
<div style={{ textAlign: "center" }}>
<DonutChart progress={chartProgress} />
<div style={{ color: "#64748B", marginTop: 16, fontSize: 16 }}>
目标完成率
</div>
</div>
</div>
</div>
</AbsoluteFill>
);
};文件:src/ProductDemo/MetricsHighlight.tsx(场景 4:数据亮点卡片)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
interface MetricCardProps {
icon: string;
value: string;
label: string;
delay: number;
}
const MetricCard: React.FC<MetricCardProps> = ({ icon, value, label, delay }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - delay,
fps,
config: { damping: 12, stiffness: 100 },
});
const translateX = interpolate(progress, [0, 1], [80, 0]);
return (
<div
style={{
width: 320,
padding: 32,
background: "linear-gradient(135deg, #1E293B, #0F172A)",
borderRadius: 20,
border: "1px solid #334155",
textAlign: "center",
transform: `translateX(${translateX}px)`,
opacity: progress,
boxShadow: "0 10px 30px rgba(0,0,0,0.3)",
}}
>
<div style={{ fontSize: 48, marginBottom: 12 }}>{icon}</div>
<div
style={{
fontSize: 42,
fontWeight: 800,
background: "linear-gradient(135deg, #3B82F6, #8B5CF6)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
}}
>
{value}
</div>
<div style={{ fontSize: 18, color: "#94A3B8", marginTop: 8 }}>
{label}
</div>
</div>
);
};
export const MetricsHighlight: React.FC = () => {
const metrics = [
{ icon: "⚡", value: "3x", label: "效率提升", delay: 0 },
{ icon: "🎯", value: "+85%", label: "决策速度", delay: 15 },
{ icon: "✅", value: "99.9%", label: "数据准确率", delay: 30 },
];
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
gap: 40,
flexDirection: "row",
}}
>
{metrics.map((m) => (
<MetricCard key={m.label} {...m} />
))}
</AbsoluteFill>
);
};文件:src/ProductDemo/CallToAction.tsx(场景 5:CTA 结尾)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
export const CallToAction: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const enterProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 120 },
});
// CTA 按钮脉冲动画
const pulse = Math.sin(frame * 0.15) * 0.05 + 1;
const glowOpacity = interpolate(
Math.sin(frame * 0.1),
[-1, 1],
[0.3, 0.8]
);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
opacity: enterProgress,
}}
>
{/* CTA 按钮 */}
<div
style={{
padding: "24px 64px",
background: "linear-gradient(135deg, #3B82F6, #8B5CF6)",
borderRadius: 16,
fontSize: 36,
fontWeight: 700,
color: "white",
transform: `scale(${enterProgress * pulse})`,
boxShadow: `0 0 60px rgba(59,130,246,${glowOpacity})`,
cursor: "pointer",
}}
>
🚀 立即免费试用
</div>
{/* 网址 */}
<div
style={{
marginTop: 24,
fontSize: 22,
color: "#64748B",
letterSpacing: 2,
}}
>
datapulse.app
</div>
</AbsoluteFill>
);
};渲染命令
# 在 Studio 中预览
npx remotion studio
# 渲染为 MP4
npx remotion render ProductDemo out/product-demo.mp4
# 渲染为 GIF(适合嵌入网页)
npx remotion render ProductDemo out/product-demo.gif --image-format=png定制指南
| 参数 | 修改位置 | 说明 |
|---|---|---|
| 产品名称 | BrandIntro.tsx 中的文字 | 替换 “DataPulse Analytics” |
| 品牌色 | 所有文件中的 #3B82F6 / #8B5CF6 | 替换为你的品牌渐变色 |
| 指标数据 | MetricsHighlight.tsx 的 metrics 数组 | 修改 value 和 label |
| 视频时长 | index.tsx 中各 Sequence 的 durationInFrames | 调整各场景时长 |
| CTA 文案 | CallToAction.tsx 中的文字 | 替换按钮文案和网址 |
💡 参数化技巧:将产品名、品牌色、指标数据等提取为
inputProps,即可用同一模板批量生成不同产品的演示视频。详见 Remotion 的 参数化渲染文档 。
3. 案例二:编程教程视频(代码演示动画)
场景描述
你是一个技术博主,需要制作一段 45 秒的编程教程视频,演示 “JavaScript 中 map、filter、reduce 的区别”。视频需要用代码动画逐行展示代码执行过程,配合数据流可视化,适合发布到 YouTube 或技术社区。
Prompt 输入
帮我用 Remotion 创建一个编程教程视频,要求:
1. 视频参数:1920x1080,30fps,时长 45 秒(1350 帧)
2. 主题:JavaScript 数组方法 map/filter/reduce 对比教程
3. 视频结构(4 个场景):
- 场景 1(0-8秒):标题卡 — "JS 数组三剑客:map · filter · reduce",带打字机效果
- 场景 2(8-22秒):map 演示 — 左侧显示代码(逐行高亮),右侧显示数据流动画
- 输入数组 [1, 2, 3, 4] 经过 x => x * 2 变换为 [2, 4, 6, 8]
- 每个元素用方块表示,经过变换管道后变色变大
- 场景 3(22-36秒):filter 演示 — 同样布局
- 输入 [1, 2, 3, 4, 5, 6] 经过 x => x % 2 === 0 过滤为 [2, 4, 6]
- 不符合条件的元素淡出消失
- 场景 4(36-45秒):reduce 演示 — 同样布局
- 输入 [10, 20, 30] 经过 (acc, x) => acc + x 累加为 60
- 元素依次合并到累加器中
4. 设计风格:暗色代码编辑器风格(类似 VS Code Dark+),代码使用等宽字体
5. 代码高亮:当前执行行用黄色边框标记,已执行行变暗
6. 动画:数据流使用 spring() 弹性动画,代码行使用逐行淡入代码输出
文件:src/Tutorial/index.tsx(主 Composition)
import React from "react";
import { AbsoluteFill, Sequence } from "remotion";
import { TitleCard } from "./TitleCard";
import { ArrayMethodDemo } from "./ArrayMethodDemo";
export const Tutorial: React.FC = () => {
return (
<AbsoluteFill style={{ backgroundColor: "#1E1E1E", fontFamily: "'Fira Code', monospace" }}>
{/* 场景 1:标题卡(0-8秒) */}
<Sequence durationInFrames={240}>
<TitleCard />
</Sequence>
{/* 场景 2:map 演示(8-22秒) */}
<Sequence from={240} durationInFrames={420}>
<ArrayMethodDemo
method="map"
code={`const nums = [1, 2, 3, 4];\nconst doubled = nums.map(\n x => x * 2\n);\n// [2, 4, 6, 8]`}
inputData={[1, 2, 3, 4]}
outputData={[2, 4, 6, 8]}
transformLabel="x => x * 2"
color="#DCDCAA"
/>
</Sequence>
{/* 场景 3:filter 演示(22-36秒) */}
<Sequence from={660} durationInFrames={420}>
<ArrayMethodDemo
method="filter"
code={`const nums = [1, 2, 3, 4, 5, 6];\nconst evens = nums.filter(\n x => x % 2 === 0\n);\n// [2, 4, 6]`}
inputData={[1, 2, 3, 4, 5, 6]}
outputData={[2, 4, 6]}
transformLabel="x => x % 2 === 0"
color="#C586C0"
/>
</Sequence>
{/* 场景 4:reduce 演示(36-45秒) */}
<Sequence from={1080} durationInFrames={270}>
<ArrayMethodDemo
method="reduce"
code={`const nums = [10, 20, 30];\nconst sum = nums.reduce(\n (acc, x) => acc + x, 0\n);\n// 60`}
inputData={[10, 20, 30]}
outputData={[60]}
transformLabel="(acc, x) => acc + x"
color="#4EC9B0"
/>
</Sequence>
</AbsoluteFill>
);
};文件:src/Tutorial/TitleCard.tsx(场景 1:打字机标题)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
export const TitleCard: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const title = "JS 数组三剑客";
const subtitle = "map · filter · reduce";
// 打字机效果:逐字显示
const titleChars = Math.floor(
interpolate(frame, [20, 80], [0, title.length], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
);
const subtitleProgress = spring({
frame: frame - 100,
fps,
config: { damping: 15, stiffness: 80 },
});
// 光标闪烁
const cursorOpacity = Math.round(Math.sin(frame * 0.3) * 0.5 + 0.5);
// 退场
const exitOpacity = interpolate(frame, [210, 240], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
opacity: exitOpacity,
}}
>
{/* 主标题 + 光标 */}
<div style={{ fontSize: 64, fontWeight: 700, color: "#D4D4D4" }}>
{title.slice(0, titleChars)}
<span style={{ opacity: cursorOpacity, color: "#569CD6" }}>|</span>
</div>
{/* 副标题:三个方法名 */}
<div
style={{
marginTop: 24,
fontSize: 36,
color: "#569CD6",
opacity: subtitleProgress,
transform: `translateY(${interpolate(subtitleProgress, [0, 1], [20, 0])}px)`,
letterSpacing: 8,
}}
>
{subtitle}
</div>
{/* 装饰线 */}
<div
style={{
marginTop: 32,
height: 3,
width: interpolate(subtitleProgress, [0, 1], [0, 400]),
background: "linear-gradient(90deg, #569CD6, #C586C0, #4EC9B0)",
borderRadius: 2,
}}
/>
</AbsoluteFill>
);
};文件:src/Tutorial/ArrayMethodDemo.tsx(通用数组方法演示组件)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
interface ArrayMethodDemoProps {
method: "map" | "filter" | "reduce";
code: string;
inputData: number[];
outputData: number[];
transformLabel: string;
color: string;
}
// 代码面板:逐行高亮显示
const CodePanel: React.FC<{ code: string; frame: number; color: string }> = ({
code,
frame,
color,
}) => {
const lines = code.split("\n");
const currentLine = Math.floor(
interpolate(frame, [0, 120], [0, lines.length], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
);
return (
<div
style={{
background: "#1E1E1E",
border: "1px solid #333",
borderRadius: 12,
padding: 32,
width: 560,
}}
>
{/* 编辑器标题栏 */}
<div style={{ display: "flex", gap: 8, marginBottom: 20 }}>
<div style={{ width: 12, height: 12, borderRadius: "50%", background: "#FF5F57" }} />
<div style={{ width: 12, height: 12, borderRadius: "50%", background: "#FEBC2E" }} />
<div style={{ width: 12, height: 12, borderRadius: "50%", background: "#28C840" }} />
</div>
{/* 代码行 */}
{lines.map((line, i) => {
const isActive = i === currentLine;
const isPast = i < currentLine;
const opacity = i <= currentLine ? 1 : 0.2;
return (
<div
key={`${line}-${i}`}
style={{
padding: "4px 12px",
fontSize: 20,
fontFamily: "'Fira Code', monospace",
color: isPast ? "#666" : "#D4D4D4",
opacity,
borderLeft: isActive ? `3px solid ${color}` : "3px solid transparent",
background: isActive ? "rgba(255,255,255,0.05)" : "transparent",
transition: "all 0.2s",
}}
>
<span style={{ color: "#666", marginRight: 16, fontSize: 14 }}>
{i + 1}
</span>
{line}
</div>
);
})}
</div>
);
};
// 数据流动画面板
const DataFlowPanel: React.FC<{
inputData: number[];
outputData: number[];
transformLabel: string;
color: string;
progress: number;
method: string;
}> = ({ inputData, outputData, transformLabel, color, progress, method }) => {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 32,
width: 500,
}}
>
{/* 输入数组 */}
<div>
<div style={{ color: "#666", fontSize: 14, marginBottom: 8, textAlign: "center" }}>
输入
</div>
<div style={{ display: "flex", gap: 12 }}>
{inputData.map((val, i) => {
const isFiltered =
method === "filter" && !outputData.includes(val);
const itemOpacity =
isFiltered
? interpolate(progress, [0.3 + i * 0.1, 0.5 + i * 0.1], [1, 0.15], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
: 1;
return (
<div
key={`in-${val}-${i}`}
style={{
width: 56,
height: 56,
borderRadius: 12,
background: "#2D2D2D",
border: "2px solid #444",
display: "flex",
justifyContent: "center",
alignItems: "center",
fontSize: 22,
fontWeight: 600,
color: "#D4D4D4",
opacity: itemOpacity,
}}
>
{val}
</div>
);
})}
</div>
</div>
{/* 变换管道 */}
<div
style={{
padding: "12px 24px",
background: `${color}22`,
border: `2px solid ${color}`,
borderRadius: 12,
color,
fontSize: 18,
fontFamily: "'Fira Code', monospace",
opacity: interpolate(progress, [0.1, 0.3], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
{transformLabel}
</div>
{/* 箭头 */}
<div style={{ fontSize: 28, color: "#444" }}>↓</div>
{/* 输出数组 */}
<div>
<div style={{ color: "#666", fontSize: 14, marginBottom: 8, textAlign: "center" }}>
输出
</div>
<div style={{ display: "flex", gap: 12 }}>
{outputData.map((val, i) => {
const itemProgress = interpolate(
progress,
[0.5 + i * 0.12, 0.7 + i * 0.12],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return (
<div
key={`out-${val}-${i}`}
style={{
width: 56,
height: 56,
borderRadius: 12,
background: `${color}33`,
border: `2px solid ${color}`,
display: "flex",
justifyContent: "center",
alignItems: "center",
fontSize: 22,
fontWeight: 700,
color,
opacity: itemProgress,
transform: `scale(${interpolate(itemProgress, [0, 1], [0.5, 1])})`,
}}
>
{val}
</div>
);
})}
</div>
</div>
</div>
);
};
export const ArrayMethodDemo: React.FC<ArrayMethodDemoProps> = ({
method,
code,
inputData,
outputData,
transformLabel,
color,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const enterProgress = spring({
frame,
fps,
config: { damping: 14, stiffness: 80 },
});
const dataProgress = interpolate(frame, [60, 360], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill
style={{
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 80,
opacity: enterProgress,
}}
>
{/* 方法名标签 */}
<div
style={{
position: "absolute",
top: 40,
left: 80,
fontSize: 28,
fontWeight: 700,
color,
padding: "8px 20px",
background: `${color}15`,
borderRadius: 8,
}}
>
.{method}()
</div>
{/* 左侧:代码面板 */}
<CodePanel code={code} frame={frame} color={color} />
{/* 右侧:数据流动画 */}
<DataFlowPanel
inputData={inputData}
outputData={outputData}
transformLabel={transformLabel}
color={color}
progress={dataProgress}
method={method}
/>
</AbsoluteFill>
);
};渲染命令
# 预览
npx remotion studio
# 渲染 1080p MP4
npx remotion render Tutorial out/js-array-tutorial.mp4
# 渲染竖版(适合手机观看)— 需要调整 Composition 尺寸为 1080x1920
npx remotion render Tutorial out/js-array-tutorial-vertical.mp4定制指南
| 参数 | 修改位置 | 说明 |
|---|---|---|
| 教程主题 | TitleCard.tsx 标题文字 | 替换为任意编程主题 |
| 代码内容 | index.tsx 中各 code prop | 替换为你要演示的代码 |
| 数据示例 | inputData / outputData | 修改演示数据 |
| 代码配色 | 各组件中的 color prop | 匹配你的代码高亮主题 |
| 字体 | 全局 fontFamily | 替换为 JetBrains Mono 等 |
💡 扩展思路:结合 AI TTS(如 ElevenLabs)生成语音旁白,用
<Audio>组件嵌入视频,即可制作带讲解的完整教程。
4. 案例三:社交媒体短视频(竖版 9:16)
场景描述
你需要为 TikTok / Instagram Reels / YouTube Shorts 制作一系列 15 秒的竖版短视频,内容是”每日一个 AI 工具推荐”。视频需要节奏快、视觉冲击力强、适合手机竖屏观看。关键是这个模板可以参数化,批量生成不同工具的推荐视频。
Prompt 输入
帮我用 Remotion 创建一个社交媒体竖版短视频模板,要求:
1. 视频参数:1080x1920(9:16 竖版),30fps,时长 15 秒(450 帧)
2. 用途:TikTok/Reels/Shorts 的 "每日 AI 工具推荐" 系列
3. 视频结构(4 个场景,节奏要快):
- 场景 1(0-3秒):Hook 开场 — 大字 "你还不知道这个 AI 工具?" 带震动效果
- 场景 2(3-8秒):工具介绍 — 工具名称(大号)、一句话描述、3 个核心功能标签
- 场景 3(8-12秒):价格对比 — 显示 "传统方案:¥XXX/月" 划掉,"AI 方案:¥XX/月" 高亮
- 场景 4(12-15秒):CTA — "关注我,每天一个 AI 神器 🔥" + 点赞动画
4. 设计风格:高饱和度渐变背景(每期换色),大字体,适合手机小屏
5. 所有文字内容通过 inputProps 参数化,方便批量生成
6. 动画要求:快节奏,大幅度运动,使用 spring() 的高 stiffness 配置代码输出
文件:src/SocialShort/index.tsx(主 Composition + 参数化)
import React from "react";
import { AbsoluteFill, Sequence } from "remotion";
import { HookOpener } from "./HookOpener";
import { ToolIntro } from "./ToolIntro";
import { PriceCompare } from "./PriceCompare";
import { SocialCTA } from "./SocialCTA";
// 参数化 Props — 每期视频只需修改这些数据
export interface SocialShortProps {
hookText: string; // Hook 文案
toolName: string; // 工具名称
toolDescription: string; // 一句话描述
features: string[]; // 3 个核心功能
oldPrice: string; // 传统方案价格
newPrice: string; // AI 方案价格
ctaText: string; // CTA 文案
gradientFrom: string; // 渐变起始色
gradientTo: string; // 渐变结束色
}
// 默认数据(Cursor 编辑器推荐)
export const defaultProps: SocialShortProps = {
hookText: "你还不知道这个 AI 工具?",
toolName: "Cursor",
toolDescription: "AI 原生代码编辑器,写代码快 10 倍",
features: ["智能补全", "多文件编辑", "一键重构"],
oldPrice: "¥599/月 (传统 IDE + 插件)",
newPrice: "¥139/月 (Pro 版)",
ctaText: "关注我,每天一个 AI 神器 🔥",
gradientFrom: "#7C3AED",
gradientTo: "#EC4899",
};
export const SocialShort: React.FC<SocialShortProps> = (props) => {
const p = { ...defaultProps, ...props };
return (
<AbsoluteFill
style={{
background: `linear-gradient(160deg, ${p.gradientFrom}, ${p.gradientTo})`,
fontFamily: "'Noto Sans SC', sans-serif",
}}
>
{/* 场景 1:Hook 开场(0-3秒,0-90帧) */}
<Sequence durationInFrames={90}>
<HookOpener text={p.hookText} />
</Sequence>
{/* 场景 2:工具介绍(3-8秒,90-240帧) */}
<Sequence from={90} durationInFrames={150}>
<ToolIntro
name={p.toolName}
description={p.toolDescription}
features={p.features}
/>
</Sequence>
{/* 场景 3:价格对比(8-12秒,240-360帧) */}
<Sequence from={240} durationInFrames={120}>
<PriceCompare oldPrice={p.oldPrice} newPrice={p.newPrice} />
</Sequence>
{/* 场景 4:CTA(12-15秒,360-450帧) */}
<Sequence from={360} durationInFrames={90}>
<SocialCTA text={p.ctaText} />
</Sequence>
</AbsoluteFill>
);
};文件:src/SocialShort/HookOpener.tsx(场景 1:震动 Hook)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
} from "remotion";
export const HookOpener: React.FC<{ text: string }> = ({ text }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// 快速弹入(高 stiffness = 快节奏)
const scale = spring({
frame,
fps,
config: { damping: 8, stiffness: 300, mass: 0.6 },
});
// 震动效果(前 20 帧)
const shakeX =
frame < 20 ? Math.sin(frame * 2.5) * (20 - frame) * 0.8 : 0;
const shakeRotate =
frame < 20 ? Math.sin(frame * 3) * (20 - frame) * 0.15 : 0;
return (
<AbsoluteFill
style={{ justifyContent: "center", alignItems: "center", padding: 60 }}
>
<div
style={{
fontSize: 72,
fontWeight: 900,
color: "white",
textAlign: "center",
lineHeight: 1.3,
transform: `scale(${scale}) translateX(${shakeX}px) rotate(${shakeRotate}deg)`,
textShadow: "0 4px 20px rgba(0,0,0,0.3)",
}}
>
{text}
</div>
{/* 装饰:闪烁 emoji */}
<div
style={{
position: "absolute",
top: 300,
right: 100,
fontSize: 80,
transform: `scale(${scale}) rotate(${frame * 2}deg)`,
}}
>
⚡
</div>
</AbsoluteFill>
);
};文件:src/SocialShort/ToolIntro.tsx(场景 2:工具介绍)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
interface ToolIntroProps {
name: string;
description: string;
features: string[];
}
export const ToolIntro: React.FC<ToolIntroProps> = ({
name,
description,
features,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const nameScale = spring({
frame,
fps,
config: { damping: 10, stiffness: 250 },
});
const descOpacity = spring({
frame: frame - 15,
fps,
config: { damping: 15, stiffness: 120 },
});
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
{/* 工具名称 */}
<div
style={{
fontSize: 96,
fontWeight: 900,
color: "white",
transform: `scale(${nameScale})`,
textShadow: "0 4px 30px rgba(0,0,0,0.4)",
}}
>
{name}
</div>
{/* 一句话描述 */}
<div
style={{
fontSize: 32,
color: "rgba(255,255,255,0.9)",
marginTop: 24,
textAlign: "center",
opacity: descOpacity,
maxWidth: 800,
}}
>
{description}
</div>
{/* 功能标签 */}
<div
style={{
display: "flex",
gap: 16,
marginTop: 48,
flexWrap: "wrap",
justifyContent: "center",
}}
>
{features.map((feat, i) => {
const tagProgress = spring({
frame: frame - 30 - i * 10,
fps,
config: { damping: 10, stiffness: 200 },
});
return (
<div
key={feat}
style={{
padding: "14px 28px",
background: "rgba(255,255,255,0.2)",
backdropFilter: "blur(10px)",
borderRadius: 50,
fontSize: 26,
fontWeight: 600,
color: "white",
transform: `scale(${tagProgress})`,
border: "1px solid rgba(255,255,255,0.3)",
}}
>
{feat}
</div>
);
})}
</div>
</AbsoluteFill>
);
};文件:src/SocialShort/PriceCompare.tsx(场景 3:价格对比)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
interface PriceCompareProps {
oldPrice: string;
newPrice: string;
}
export const PriceCompare: React.FC<PriceCompareProps> = ({
oldPrice,
newPrice,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const oldPriceProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 200 },
});
// 划线动画(延迟出现)
const strikeWidth = interpolate(frame, [30, 50], [0, 100], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const newPriceProgress = spring({
frame: frame - 40,
fps,
config: { damping: 8, stiffness: 250 },
});
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
padding: 60,
gap: 48,
}}
>
{/* 传统方案(划掉) */}
<div
style={{
position: "relative",
opacity: oldPriceProgress,
transform: `translateY(${interpolate(oldPriceProgress, [0, 1], [40, 0])}px)`,
}}
>
<div
style={{
fontSize: 36,
color: "rgba(255,255,255,0.5)",
textAlign: "center",
}}
>
传统方案
</div>
<div
style={{
fontSize: 48,
fontWeight: 700,
color: "rgba(255,255,255,0.6)",
marginTop: 8,
}}
>
{oldPrice}
</div>
{/* 红色划线 */}
<div
style={{
position: "absolute",
top: "60%",
left: 0,
width: `${strikeWidth}%`,
height: 4,
background: "#EF4444",
borderRadius: 2,
transform: "rotate(-5deg)",
}}
/>
</div>
{/* VS 分隔 */}
<div style={{ fontSize: 32, color: "rgba(255,255,255,0.4)" }}>VS</div>
{/* AI 方案(高亮) */}
<div
style={{
transform: `scale(${newPriceProgress})`,
textAlign: "center",
}}
>
<div style={{ fontSize: 36, color: "rgba(255,255,255,0.9)" }}>
AI 方案
</div>
<div
style={{
fontSize: 64,
fontWeight: 900,
color: "#FDE047",
marginTop: 8,
textShadow: "0 0 40px rgba(253,224,71,0.5)",
}}
>
{newPrice}
</div>
<div
style={{
marginTop: 16,
fontSize: 28,
color: "#22C55E",
fontWeight: 700,
}}
>
省 80%+ 💰
</div>
</div>
</AbsoluteFill>
);
};文件:src/SocialShort/SocialCTA.tsx(场景 4:CTA + 点赞动画)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
} from "remotion";
export const SocialCTA: React.FC<{ text: string }> = ({ text }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({
frame,
fps,
config: { damping: 10, stiffness: 200 },
});
// 点赞按钮弹跳动画
const likeScale = spring({
frame: frame - 20,
fps,
config: { damping: 6, stiffness: 300, mass: 0.5 },
});
// 飘散的爱心粒子
const hearts = Array.from({ length: 6 }, (_, i) => ({
id: i,
startFrame: 30 + i * 5,
x: -30 + i * 60,
speed: 1.5 + Math.random(),
}));
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
{/* CTA 文案 */}
<div
style={{
fontSize: 48,
fontWeight: 800,
color: "white",
textAlign: "center",
lineHeight: 1.4,
transform: `scale(${textProgress})`,
textShadow: "0 4px 20px rgba(0,0,0,0.3)",
}}
>
{text}
</div>
{/* 点赞按钮 */}
<div
style={{
marginTop: 48,
fontSize: 80,
transform: `scale(${likeScale})`,
}}
>
❤️
</div>
{/* 飘散爱心 */}
{hearts.map((heart) => {
const heartFrame = frame - heart.startFrame;
if (heartFrame < 0) return null;
const y = -heartFrame * heart.speed * 3;
const opacity = interpolate(heartFrame, [0, 40], [1, 0], {
extrapolateRight: "clamp",
});
const scale = interpolate(heartFrame, [0, 20], [0.5, 1], {
extrapolateRight: "clamp",
});
return (
<div
key={heart.id}
style={{
position: "absolute",
bottom: 600,
left: 540 + heart.x,
fontSize: 36,
transform: `translateY(${y}px) scale(${scale})`,
opacity,
}}
>
❤️
</div>
);
})}
{/* 底部提示 */}
<div
style={{
position: "absolute",
bottom: 120,
fontSize: 24,
color: "rgba(255,255,255,0.6)",
opacity: interpolate(frame, [40, 60], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
双击点赞 👆 关注不迷路
</div>
</AbsoluteFill>
);
};批量生成配置
参数化是这个案例的核心价值。创建一个数据文件,即可批量生成整个系列:
文件:src/SocialShort/batch-data.ts
import { SocialShortProps } from "./index";
// 一周 7 期的数据
export const weeklyTools: SocialShortProps[] = [
{
hookText: "你还不知道这个 AI 工具?",
toolName: "Cursor",
toolDescription: "AI 原生代码编辑器,写代码快 10 倍",
features: ["智能补全", "多文件编辑", "一键重构"],
oldPrice: "¥599/月 (IDE + 插件)",
newPrice: "¥139/月",
ctaText: "关注我,每天一个 AI 神器 🔥",
gradientFrom: "#7C3AED",
gradientTo: "#EC4899",
},
{
hookText: "设计师要失业了?",
toolName: "v0.dev",
toolDescription: "用自然语言生成 React UI 组件",
features: ["秒出 UI", "可编辑代码", "响应式"],
oldPrice: "¥2000/月 (设计师外包)",
newPrice: "¥0 (免费层)",
ctaText: "关注我,每天一个 AI 神器 🔥",
gradientFrom: "#059669",
gradientTo: "#0EA5E9",
},
{
hookText: "写文档还在手动?",
toolName: "Notion AI",
toolDescription: "AI 驱动的知识管理和文档写作",
features: ["自动总结", "翻译", "头脑风暴"],
oldPrice: "¥500/月 (文案外包)",
newPrice: "¥72/月",
ctaText: "关注我,每天一个 AI 神器 🔥",
gradientFrom: "#DC2626",
gradientTo: "#F97316",
},
// ... 继续添加更多工具
];渲染命令
# 预览单个视频
npx remotion studio
# 渲染单个视频(竖版 9:16)
npx remotion render SocialShort out/ai-tool-cursor.mp4
# 批量渲染(使用 Remotion 的参数化渲染)
# 方法 1:命令行传参
npx remotion render SocialShort out/tool-v0.mp4 \
--props='{"toolName":"v0.dev","gradientFrom":"#059669","gradientTo":"#0EA5E9"}'
# 方法 2:使用 Node.js 脚本批量渲染
npx ts-node scripts/batch-render.ts文件:scripts/batch-render.ts(批量渲染脚本)
import { bundle } from "@remotion/bundler";
import { renderMedia, selectComposition } from "@remotion/renderer";
import { weeklyTools } from "../src/SocialShort/batch-data";
async function batchRender() {
const bundled = await bundle({ entryPoint: "./src/index.ts" });
for (let i = 0; i < weeklyTools.length; i++) {
const tool = weeklyTools[i];
const composition = await selectComposition({
serveUrl: bundled,
id: "SocialShort",
inputProps: tool,
});
const outputPath = `out/ai-tool-${tool.toolName.toLowerCase()}.mp4`;
console.log(`渲染中 [${i + 1}/${weeklyTools.length}]: ${tool.toolName}`);
await renderMedia({
composition,
serveUrl: bundled,
codec: "h264",
outputLocation: outputPath,
inputProps: tool,
});
console.log(`✅ 完成: ${outputPath}`);
}
}
batchRender().catch(console.error);定制指南
| 参数 | 修改位置 | 说明 |
|---|---|---|
| 视频尺寸 | Composition 的 width/height | 1080x1920(竖版)或 1080x1080(方形) |
| 系列数据 | batch-data.ts | 添加更多工具数据即可批量生成 |
| 背景渐变 | gradientFrom / gradientTo | 每期换色保持新鲜感 |
| 动画节奏 | spring 的 stiffness 参数 | 值越大动画越快越有冲击力 |
| 品牌水印 | 在主 Composition 添加固定层 | 所有场景都会显示 |
💡 生产技巧:配合 Remotion Lambda,可以并行渲染一周 7 期视频,总耗时不到 2 分钟。详见 12e-成本与生产部署。
5. Prompt 编写技巧与模板
三个案例背后有一套通用的 prompt 编写方法论。掌握这些技巧,你可以为任何视频场景写出高质量的 Remotion prompt。
提示词模板:通用视频生成 Prompt
帮我用 Remotion 创建一个 [视频类型] 视频,要求:
1. 视频参数:[宽度]x[高度],[帧率]fps,时长 [秒数] 秒([总帧数] 帧)
2. 用途:[具体使用场景和发布平台]
3. 视频结构([N] 个场景):
- 场景 1([起始秒]-[结束秒]秒):[场景名称] — [具体内容描述]
- 场景 2(...):...
- ...
4. 设计风格:[配色方案],[字体风格],[整体氛围]
5. 动画要求:[动画类型和节奏描述]
6. 数据参数化:[哪些内容需要通过 inputProps 参数化]
7. 特殊要求:[音频、字幕、水印等额外需求]提示词模板:迭代优化 Prompt
当 AI 生成的第一版代码不完全满意时,使用以下模板进行迭代:
请修改 [文件名] 中的 [组件名]:
当前问题:[描述具体问题,如"动画太慢"、"颜色对比度不够"、"文字被裁切"]
期望效果:[描述你想要的效果]
参考:[如果有参考视频或设计稿,描述其风格]
请只修改相关部分,保持其他代码不变。提示词模板:批量生成系列视频
基于现有的 [模板名称] 视频模板,帮我:
1. 将所有可变内容提取为 inputProps 参数
2. 创建一个 TypeScript 类型定义文件,定义所有参数
3. 创建一个 batch-data.ts 数据文件,包含 [N] 期的数据
4. 创建一个 batch-render.ts 批量渲染脚本
5. 每期数据:
- 第 1 期:[具体数据]
- 第 2 期:[具体数据]
- ...Prompt 编写最佳实践
| 技巧 | 说明 | 示例 |
|---|---|---|
| 明确帧数 | 用帧数而非秒数定义时间点,避免歧义 | ”场景 1(0-150帧)” 而非 “前 5 秒” |
| 指定 spring 参数 | 描述动画感觉,让 AI 选择合适的 damping/stiffness | ”快速弹入,有轻微回弹” |
| 分文件要求 | 明确要求每个场景独立文件 | ”将所有场景组件放在独立文件中” |
| 颜色用十六进制 | 避免模糊的颜色描述 | ”#3B82F6” 而非 “蓝色” |
| 描述退场动画 | 很多人忘记场景退出效果 | ”最后 15 帧淡出到透明” |
避坑指南
❌ 常见错误
-
在 spring() 中使用负 frame 值导致动画不触发
- 问题:
spring({ frame: frame - 100, fps })当 frame < 100 时返回 0,但不会报错,导致元素”消失” - 正确做法:使用
<Sequence from={100}>包裹组件,让组件内部的 frame 从 0 开始计数
- 问题:
-
忘记设置 extrapolateLeft/Right 导致数值溢出
- 问题:
interpolate(frame, [0, 30], [0, 1])当 frame > 30 时会继续增长超过 1 - 正确做法:添加
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }限制输出范围
- 问题:
-
竖版视频忘记调整 Composition 尺寸
- 问题:在 Root.tsx 中 Composition 仍然是 1920x1080,但组件按 1080x1920 布局
- 正确做法:确保 Composition 的
width={1080} height={1920}与组件布局一致
-
批量渲染时 inputProps 类型不匹配
- 问题:命令行
--props传入的 JSON 字段名与组件 Props 不一致 - 正确做法:定义统一的 TypeScript 接口,在 Composition 和数据文件中共享类型
- 问题:命令行
-
SVG 动画在渲染时与预览不一致
- 问题:某些 CSS 动画(transition、animation)在 Remotion 渲染时不生效,因为每帧是独立截图
- 正确做法:所有动画必须基于
useCurrentFrame()声明式计算,不要依赖 CSS transition
-
中文字体在服务器渲染时显示方块
- 问题:服务器环境没有安装中文字体,渲染出的视频中文显示为方块
- 正确做法:使用
@remotion/google-fonts加载网络字体,或在 Dockerfile 中安装fonts-noto-cjk
-
视频文件体积过大
- 问题:30 秒 1080p 视频渲染出 200MB+ 的文件
- 正确做法:使用
--crf参数控制压缩率(推荐 18-23),或用 FFmpeg 后处理压缩
✅ 最佳实践
- 先用 Studio 预览,确认效果后再渲染:渲染一个 30 秒视频需要 1-3 分钟,频繁渲染浪费时间
- 每个场景独立组件:便于单独调试和复用,也方便 AI 理解和修改
- 善用
<Sequence>的from和durationInFrames:这是 Remotion 时间编排的核心,比手动计算帧偏移更清晰 - 参数化一切可变内容:产品名、颜色、数据、文案都应该是
inputProps,这样一个模板可以生成无限变体 - 动画使用
spring()而非interpolate():spring 提供更自然的物理动画效果,interpolate 适合线性过渡 - 为 AI 迭代保留清晰的文件结构:当你需要 AI 修改某个场景时,独立文件让 AI 只需关注一个组件
相关资源与延伸阅读
以下资源可以帮助你深入学习 Remotion 视频开发和 AI 辅助视频生成:
- Remotion 官方文档 — 最权威的 API 参考和教程,包含所有组件和 Hook 的详细说明
- Remotion Agent Skills GitHub 仓库 — Claude Code 的 Remotion Skills 源码,了解 AI 如何学习写视频代码
- Remotion AI 代码生成指南 — 官方的 LLM 集成文档,包含 System Prompt 和代码生成最佳实践
- Remotion 模板库 — 官方和社区贡献的视频模板,可直接克隆使用
- Remotion Lambda 部署指南 — AWS Lambda 云渲染的完整设置教程
- YouTube Shorts 生成器实战 — 使用 Remotion + Claude Code 构建 YouTube Shorts 自动化管线的完整教程
- Claude Code + Remotion 视频制作指南 — 5 分钟从零开始用 Claude Code 创建视频的快速入门
- Remotion 参数化渲染文档 — 批量生成视频的核心技术,详解 inputProps 和数据驱动渲染
- Motion Canvas — Remotion 的替代方案,基于 TypeScript 的动画引擎,适合纯动画场景
- Bazaar — AI 产品演示视频生成 — 基于 Remotion 的 AI 产品演示视频自动生成工具
参考来源
- Remotion 官方文档 — AI 代码生成 (2025-06)
- Make videos with Claude Code: Remotion AI video makes production code from plain prompts (2026-02)
- How To Turn Claude Code into a Video Director with Remotion (2025-01)
- I Built a YouTube Shorts Generator with AI (Remotion + Claude Code) (2026-02)
- Create Animated Videos with Claude Code & Remotion for Ads (2025-06)
- Claude Code Can Make Videos Now (Full Guide) (2025-06)
- Remotion — Code Your Way to Automated Video Production at Scale (2025-07)
- Create a Video with Claude Code and Remotion in 5 minutes (2025-07)
- Remotion 官方文档 — spring() 动画 (2025-06)
- Remotion 官方文档 — Sequence 组件 (2025-06)
📖 返回 总览与导航 | 上一节:12c-端到端工作流 | 下一节:12e-成本与生产部署