Skip to Content

12d - 实战案例集

本文是《AI Agent 实战手册》第 12 章第 4 节。 上一节:12c-端到端工作流 | 下一节:12e-成本与生产部署

概述

理论和工作流讲完了,现在上真刀真枪。本节提供三个完整的 Remotion + AI Agent 实战案例——产品演示视频、编程教程视频、社交媒体短视频——每个案例都包含完整的自然语言 prompt 输入和 AI 生成的 React/TypeScript 代码输出,你可以直接复制到项目中运行。这些案例覆盖了最常见的代码驱动视频场景,帮你快速建立从 prompt 到成品视频的实操经验。


1. 案例总览与工具准备

工具推荐

工具用途价格适用场景
Remotion v4.xReact 视频框架个人及 ≤3 人公司免费;企业需 Company License所有案例的核心渲染引擎
Claude Code + Remotion Agent SkillsAI 代码生成Claude Pro $20/月;Max $100-200/月从 prompt 生成视频代码
Remotion Studio本地实时预览免费(含在 Remotion 中)开发调试阶段
Google Fonts / @remotion/google-fonts字体加载免费视频中的文字排版
Remotion LambdaAWS 云渲染框架免费(AWS 费用约 $0.01-0.05/视频)批量渲染和生产部署
FFmpeg视频后处理免费(开源)格式转换、压缩
Unsplash / Pexels API免版权素材免费案例中的背景图片
ElevenLabs / OpenAI TTSAI 语音旁白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.tsxmetrics 数组修改 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/height1080x1920(竖版)或 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 帧淡出到透明”

避坑指南

❌ 常见错误

  1. 在 spring() 中使用负 frame 值导致动画不触发

    • 问题:spring({ frame: frame - 100, fps }) 当 frame < 100 时返回 0,但不会报错,导致元素”消失”
    • 正确做法:使用 <Sequence from={100}> 包裹组件,让组件内部的 frame 从 0 开始计数
  2. 忘记设置 extrapolateLeft/Right 导致数值溢出

    • 问题:interpolate(frame, [0, 30], [0, 1]) 当 frame > 30 时会继续增长超过 1
    • 正确做法:添加 { extrapolateLeft: "clamp", extrapolateRight: "clamp" } 限制输出范围
  3. 竖版视频忘记调整 Composition 尺寸

    • 问题:在 Root.tsx 中 Composition 仍然是 1920x1080,但组件按 1080x1920 布局
    • 正确做法:确保 Composition 的 width={1080} height={1920} 与组件布局一致
  4. 批量渲染时 inputProps 类型不匹配

    • 问题:命令行 --props 传入的 JSON 字段名与组件 Props 不一致
    • 正确做法:定义统一的 TypeScript 接口,在 Composition 和数据文件中共享类型
  5. SVG 动画在渲染时与预览不一致

    • 问题:某些 CSS 动画(transition、animation)在 Remotion 渲染时不生效,因为每帧是独立截图
    • 正确做法:所有动画必须基于 useCurrentFrame() 声明式计算,不要依赖 CSS transition
  6. 中文字体在服务器渲染时显示方块

    • 问题:服务器环境没有安装中文字体,渲染出的视频中文显示为方块
    • 正确做法:使用 @remotion/google-fonts 加载网络字体,或在 Dockerfile 中安装 fonts-noto-cjk
  7. 视频文件体积过大

    • 问题:30 秒 1080p 视频渲染出 200MB+ 的文件
    • 正确做法:使用 --crf 参数控制压缩率(推荐 18-23),或用 FFmpeg 后处理压缩

✅ 最佳实践

  1. 先用 Studio 预览,确认效果后再渲染:渲染一个 30 秒视频需要 1-3 分钟,频繁渲染浪费时间
  2. 每个场景独立组件:便于单独调试和复用,也方便 AI 理解和修改
  3. 善用 <Sequence>fromdurationInFrames:这是 Remotion 时间编排的核心,比手动计算帧偏移更清晰
  4. 参数化一切可变内容:产品名、颜色、数据、文案都应该是 inputProps,这样一个模板可以生成无限变体
  5. 动画使用 spring() 而非 interpolate():spring 提供更自然的物理动画效果,interpolate 适合线性过渡
  6. 为 AI 迭代保留清晰的文件结构:当你需要 AI 修改某个场景时,独立文件让 AI 只需关注一个组件

相关资源与延伸阅读

以下资源可以帮助你深入学习 Remotion 视频开发和 AI 辅助视频生成:

  1. Remotion 官方文档  — 最权威的 API 参考和教程,包含所有组件和 Hook 的详细说明
  2. Remotion Agent Skills GitHub 仓库  — Claude Code 的 Remotion Skills 源码,了解 AI 如何学习写视频代码
  3. Remotion AI 代码生成指南  — 官方的 LLM 集成文档,包含 System Prompt 和代码生成最佳实践
  4. Remotion 模板库  — 官方和社区贡献的视频模板,可直接克隆使用
  5. Remotion Lambda 部署指南  — AWS Lambda 云渲染的完整设置教程
  6. YouTube Shorts 生成器实战  — 使用 Remotion + Claude Code 构建 YouTube Shorts 自动化管线的完整教程
  7. Claude Code + Remotion 视频制作指南  — 5 分钟从零开始用 Claude Code 创建视频的快速入门
  8. Remotion 参数化渲染文档  — 批量生成视频的核心技术,详解 inputProps 和数据驱动渲染
  9. Motion Canvas  — Remotion 的替代方案,基于 TypeScript 的动画引擎,适合纯动画场景
  10. Bazaar — AI 产品演示视频生成  — 基于 Remotion 的 AI 产品演示视频自动生成工具

参考来源


📖 返回 总览与导航 | 上一节:12c-端到端工作流 | 下一节:12e-成本与生产部署

Last updated on