| name | remotion-card-video |
| description | 用 Remotion + React 生成「极简知识卡片流」风格的视频。当用户说「帮我做成视频」「生成 Remotion 脚本」「把这篇文章做成卡片视频」「出 TSX 代码」时触发。内置设计系统、动效规则和场景模板,适配「克制而不僵硬,活泼而不轻浮」的视觉风格。 |
Remotion 极简知识卡片流 Skill
角色定位
你是一位熟悉 Remotion 框架的视频工程师,专门把知识类内容翻译成「极简知识卡片流」风格的可渲染视频。你输出的是可以直接跑起来的 TSX 代码,不是伪代码或示意图。
核心美学原则:克制而不僵硬,活泼而不轻浮。
- 克制:留白充分,信息密度低,每张卡片只讲一件事
- 不僵硬:用 spring 动效,元素有重量感,不是机械位移
- 活泼:节奏有变化,关键词有高亮,偶尔有小细节让人会心一笑
- 不轻浮:字体偏重,颜色不超过 3 色,没有多余装饰
设计系统(Design Tokens)
每次生成视频时,在文件顶部定义这些变量,所有组件必须引用这里,不允许硬编码颜色或字号。
export const COLORS = {
bg: '#0A0A0F',
surface: '#13131F',
surfaceHover: '#1A1A2E',
primary: '#EAEAF0',
secondary: '#6B6B8A',
accent: '#5B8CFF',
warm: '#F5A94A',
border: '#1E1E30',
dim: '#2A2A40',
};
export const TYPE = {
hero: 88,
h1: 60,
h2: 44,
h3: 32,
body: 26,
caption: 20,
label: 16,
};
export const FONT = {
sans: '"PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif',
mono: '"JetBrains Mono", "Fira Code", monospace',
};
export const SPACING = {
cardPadding: 80,
sectionGap: 48,
lineHeight: 1.55,
};
动效规则(Animation Rules)
Spring 配置
「克制活泼」的 spring 参数:damping 高(不弹跳),stiffness 中(有响应感)。
import { spring, useVideoConfig } from 'remotion';
const SPRING_ENTER = { damping: 22, stiffness: 90, mass: 1 };
const SPRING_SLOW = { damping: 28, stiffness: 60, mass: 1.2 };
const SPRING_POP = { damping: 14, stiffness: 120, mass: 0.8 };
const { fps } = useVideoConfig();
const progress = spring({ fps, frame, config: SPRING_ENTER, durationInFrames: 20 });
入场动效模板
const fadeUpStyle = (frame: number, fps: number, delay = 0) => {
const f = Math.max(0, frame - delay);
const progress = spring({ fps, frame: f, config: SPRING_ENTER, durationInFrames: 20 });
return {
opacity: interpolate(progress, [0, 1], [0, 1]),
transform: `translateY(${interpolate(progress, [0, 1], [24, 0])}px)`,
};
};
const fadeLeftStyle = (frame: number, fps: number, delay = 0) => {
const f = Math.max(0, frame - delay);
const progress = spring({ fps, frame: f, config: SPRING_ENTER, durationInFrames: 18 });
return {
opacity: interpolate(progress, [0, 1], [0, 1]),
transform: `translateX(${interpolate(progress, [0, 1], [-20, 0])}px)`,
};
};
const fadeStyle = (frame: number, fps: number, delay = 0) => {
const f = Math.max(0, frame - delay);
const opacity = interpolate(f, [0, 18], [0, 1], { extrapolateRight: 'clamp' });
return { opacity };
};
节奏规则
- 每张卡片默认时长:90 帧(3 秒 @ 30fps)
- 卡片切换留空:15 帧(0.5 秒),用纯色过渡,不用转场特效
- 文字入场延迟递进:每行 +8 帧(营造阅读节奏感)
- 封面停留:60 帧(2 秒)
- 结尾停留:90 帧(3 秒)
视频合成规范
export default {
fps: 30,
width: 1920,
height: 1080,
};
标准场景结构
import { Composition } from 'remotion';
import { MyVideo } from './MyVideo';
export const RemotionRoot = () => (
<>
<Composition
id="SkillIsAllYouNeed"
component={MyVideo}
durationInFrames={/* 计算所有场景帧数之和 */}
fps={30}
width={1920}
height={1080}
/>
</>
);
场景组件模板
封面卡片(Cover)
export const CoverScene: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const titleStyle = fadeUpStyle(frame, fps, 0);
const subtitleStyle = fadeUpStyle(frame, fps, 12);
const tagStyle = fadeStyle(frame, fps, 24);
return (
<AbsoluteFill style={{ background: COLORS.bg, justifyContent: 'center', alignItems: 'center' }}>
{/* 顶部极细装饰线 */}
<div style={{
position: 'absolute', top: 80, left: 80, right: 80,
height: 1, background: COLORS.dim, ...fadeStyle(frame, fps, 0)
}} />
<div style={{ textAlign: 'center', maxWidth: 1200 }}>
{/* 主标题 */}
<div style={{ ...titleStyle, fontSize: TYPE.hero, fontFamily: FONT.sans,
color: COLORS.primary, fontWeight: 700, letterSpacing: '-2px', lineHeight: 1.1 }}>
Skill Is All You Need
</div>
{/* 副标题 */}
<div style={{ ...subtitleStyle, fontSize: TYPE.h3, color: COLORS.secondary,
fontFamily: FONT.sans, marginTop: 32, fontWeight: 400 }}>
让 AI 记住你的规则,而不是每次重新教
</div>
{/* 标签 */}
<div style={{ ...tagStyle, display: 'flex', gap: 16, justifyContent: 'center', marginTop: 48 }}>
{['AI工具', 'Cowork', 'SKILL_INDEX'].map(tag => (
<span key={tag} style={{
fontSize: TYPE.label, color: COLORS.accent, fontFamily: FONT.mono,
border: `1px solid ${COLORS.accent}`, borderRadius: 4,
padding: '6px 14px', opacity: 0.8,
}}>{tag}</span>
))}
</div>
</div>
{/* 底部极细装饰线 */}
<div style={{
position: 'absolute', bottom: 80, left: 80, right: 80,
height: 1, background: COLORS.dim, ...fadeStyle(frame, fps, 0)
}} />
</AbsoluteFill>
);
};
知识卡片(KnowledgeCard)
interface KnowledgeCardProps {
frame: number;
fps: number;
label?: string;
title: string;
body: string;
highlight?: string;
accentColor?: string;
}
export const KnowledgeCard: React.FC<KnowledgeCardProps> = ({
frame, fps, label, title, body, highlight, accentColor = 'cold'
}) => {
const color = accentColor === 'warm' ? COLORS.warm : COLORS.accent;
const labelStyle = fadeStyle(frame, fps, 0);
const titleStyle = fadeUpStyle(frame, fps, 6);
const bodyStyle = fadeUpStyle(frame, fps, 16);
return (
<AbsoluteFill style={{ background: COLORS.bg, justifyContent: 'center', alignItems: 'center' }}>
<div style={{
background: COLORS.surface,
border: `1px solid ${COLORS.border}`,
borderRadius: 16,
padding: SPACING.cardPadding,
maxWidth: 1280, width: '80%',
borderLeft: `3px solid ${color}`,
}}>
{label && (
<div style={{ ...labelStyle, fontSize: TYPE.label, color, fontFamily: FONT.mono,
marginBottom: 24, letterSpacing: '2px', textTransform: 'uppercase' }}>
{label}
</div>
)}
<div style={{ ...titleStyle, fontSize: TYPE.h2, color: COLORS.primary,
fontFamily: FONT.sans, fontWeight: 600, lineHeight: 1.3, marginBottom: 32 }}>
{title}
</div>
<div style={{ ...bodyStyle, fontSize: TYPE.body, color: COLORS.secondary,
fontFamily: FONT.sans, lineHeight: SPACING.lineHeight }}>
{body}
</div>
</div>
</AbsoluteFill>
);
};
代码展示卡片(CodeCard)
export const CodeCard: React.FC<{ frame: number; fps: number; code: string; caption?: string }> = ({
frame, fps, code, caption
}) => {
return (
<AbsoluteFill style={{ background: COLORS.bg, justifyContent: 'center', alignItems: 'center' }}>
<div style={{ width: '80%', maxWidth: 1280 }}>
<div style={{
...fadeUpStyle(frame, fps, 0),
background: COLORS.surfaceHover,
border: `1px solid ${COLORS.border}`,
borderRadius: 12,
padding: '48px 56px',
fontFamily: FONT.mono,
fontSize: TYPE.caption,
color: COLORS.primary,
lineHeight: 1.8,
whiteSpace: 'pre',
}}>
{code}
</div>
{caption && (
<div style={{
...fadeStyle(frame, fps, 20),
fontSize: TYPE.label, color: COLORS.secondary,
fontFamily: FONT.sans, marginTop: 24, textAlign: 'center',
}}>
{caption}
</div>
)}
</div>
</AbsoluteFill>
);
};
结尾卡片(Outro)
export const OutroScene: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => (
<AbsoluteFill style={{ background: COLORS.bg, justifyContent: 'center', alignItems: 'center' }}>
<div style={{ textAlign: 'center' }}>
<div style={{ ...fadeUpStyle(frame, fps, 0), fontSize: TYPE.h1,
color: COLORS.primary, fontFamily: FONT.sans, fontWeight: 700 }}>
Skill Is All You Need
</div>
<div style={{ ...fadeUpStyle(frame, fps, 16), fontSize: TYPE.body,
color: COLORS.secondary, fontFamily: FONT.sans, marginTop: 24 }}>
把你的流程固化成手册,让 AI 每次都能立刻上手
</div>
<div style={{ ...fadeStyle(frame, fps, 30), display: 'flex',
gap: 40, justifyContent: 'center', marginTop: 56 }}>
<span style={{ fontSize: TYPE.caption, color: COLORS.accent, fontFamily: FONT.mono }}>
@跳蛛先生
</span>
<span style={{ fontSize: TYPE.caption, color: COLORS.dim, fontFamily: FONT.mono }}>·</span>
<span style={{ fontSize: TYPE.caption, color: COLORS.secondary, fontFamily: FONT.mono }}>
D:\AIGC工作站\知识库
</span>
</div>
</div>
</AbsoluteFill>
);
渲染命令
npx remotion studio
npx remotion render src/index.ts SkillIsAllYouNeed out/skill-is-all-you-need.mp4
npx remotion render src/index.ts SkillIsAllYouNeedVertical out/skill-vertical.mp4 --width=1080 --height=1920
npx remotion render src/index.ts SkillIsAllYouNeed out/final.mp4 --concurrency=4
禁止行为
- ❌ 硬编码颜色和字号(必须用 design tokens)
- ❌ 使用 CSS transition 或 keyframes(全部用 Remotion 的 interpolate/spring)
- ❌ 每张卡片放超过 3 个信息点
- ❌ 使用多于 3 种颜色(bg / primary / 一个 accent)
- ❌ 添加阴影、渐变、模糊等装饰效果(克制原则)
- ❌ 输出不可运行的伪代码
- ❌ 忘记计算 durationInFrames(必须精确到帧)
新建项目流程(首次使用)
npx create-video@latest
cd my-video && npm install
npm run dev
npx remotion studio
npx remotion render src/index.ts YourCompositionId out/video.mp4