比特之外

Remotion 教程 01:为什么用 React 生成视频

如果你会 React,又想做可复用、可批量生成的视频,Remotion 是一个很自然的选择。

它不是让你在网页里播放动画再录屏,而是让你用 React 组件描述视频画面。Remotion 会按照固定帧率,一帧一帧渲染这些组件,最后合成视频文件。

这篇先不讲复杂生态,也不讲 AI 成片。我们先解决第一件事:Remotion 到底是什么,一个视频入口怎么定义,为什么 frame 是视频动画的核心。

Remotion 适合什么

Remotion 可以理解为“用 React 写视频”的工具链。你写的仍然是组件、props、样式和 TypeScript,但最终产物不是网页,而是视频。

它适合这些场景:

  • 图文短视频模板
  • 产品介绍视频
  • 口播字幕视频
  • 数据报告视频
  • AI 批量生成视频
  • 需要参数化换标题、换素材、换主题色的视频

传统剪辑软件适合一次性创作。Remotion 更适合把视频做成模板:输入不同数据,生成不同视频。

它和网页动画有什么不同

网页动画通常关心“浏览器经过了多少时间”。例如 CSS transition、CSS animation、Motion 的 runtime 动画,都很适合网页交互。

但视频生成更关心这些问题:

第 0 帧是什么画面?
第 1 帧是什么画面?
第 2 帧是什么画面?

Remotion 会把 React 组件放到浏览器里渲染,但它最终要得到的是一张张确定的画面。只要输入数据和帧号一样,同一帧就应该得到同样的结果。

这就是后面会反复出现的原则:

用 frame 决定画面状态

一个视频入口叫 Composition

在 Remotion 里,一个可预览、可渲染的视频入口叫 Composition

你可以把它理解成“一个视频模板的注册信息”。它会告诉 Remotion:

  • 这个视频叫什么
  • 由哪个 React 组件负责画面
  • 总共有多少帧
  • 每秒多少帧
  • 输出尺寸是多少

最小形式大概是这样:

<Composition
  id="CoreBasics"
  component={CoreBasics}
  durationInFrames={150}
  fps={30}
  width={1920}
  height={1080}
/>

这段配置表示:

  • id="CoreBasics":视频入口名称
  • component={CoreBasics}:画面由 CoreBasics 组件渲染
  • durationInFrames={150}:总共 150 帧
  • fps={30}:每秒 30 帧
  • width={1920} / height={1080}:输出 1080p 横屏视频

150 帧除以 30fps,就是 5 秒。

组件怎么知道当前是第几帧

视频画面最终还是 React 组件。不同的是,组件里可以用 useCurrentFrame() 拿到当前帧号:

const frame = useCurrentFrame();

如果 Remotion 正在渲染第 0 帧,frame 就是 0。 如果正在渲染第 60 帧,frame 就是 60。

拿到 frame 之后,就可以把帧号转换成透明度、位移、缩放、旋转等样式值。

例如:

const opacity = interpolate(frame, [24, 44], [0, 1], {
  extrapolateLeft: "clamp",
  extrapolateRight: "clamp",
});

这段代码表示:

  • 第 24 帧之前,透明度是 0
  • 第 24 到第 44 帧之间,透明度从 0 变成 1
  • 第 44 帧之后,透明度保持 1

这里没有 CSS transition。每一帧的透明度都是由当前 frame 算出来的。

Sequence 用来安排时间轴

除了让元素渐显,视频里还经常需要安排“某段内容从第几帧开始出现”。

Remotion 里可以用 Sequence

<Sequence from={36}>
  <div>这段内容从第 36 帧开始出现</div>
</Sequence>

from={36} 表示里面的内容从第 36 帧开始进入时间轴。

它不是让浏览器等待真实时间,而是告诉 Remotion:这段内容在视频时间轴上的起点是第 36 帧。

完整最小示例

前面讲完概念后,再看一个完整例子会更清楚。

这个例子包含两个文件:

src/Root.tsx
src/CoreBasics.tsx

先注册视频入口:

// src/Root.tsx
import { Composition } from "remotion";
import { CoreBasics } from "./CoreBasics";
 
export const RemotionRoot = () => {
  return (
    <Composition
      id="CoreBasics"
      component={CoreBasics}
      durationInFrames={150}
      fps={30}
      width={1920}
      height={1080}
    />
  );
};

再写真正的画面组件:

// src/CoreBasics.tsx
import React from "react";
import {
  AbsoluteFill,
  Sequence,
  interpolate,
  useCurrentFrame,
} from "remotion";
 
export const CoreBasics: React.FC = () => {
  const frame = useCurrentFrame();
 
  const opacity = interpolate(frame, [24, 44], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
 
  return (
    <AbsoluteFill
      style={{
        backgroundColor: "#f8fafc",
        padding: 96,
        fontFamily: "Inter, sans-serif",
      }}
    >
      <h1 style={{ margin: 0, fontSize: 72 }}>
        视频由一帧一帧组成
      </h1>
 
      <Sequence from={36}>
        <div
          style={{
            marginTop: 120,
            display: "grid",
            gridTemplateColumns: "repeat(4, 1fr)",
            gap: 24,
            opacity,
          }}
        >
          {["Composition", "fps", "frame", "Sequence"].map((label) => (
            <div
              key={label}
              style={{
                padding: 32,
                borderRadius: 8,
                backgroundColor: "#2563eb",
                color: "white",
                fontSize: 34,
                fontWeight: 800,
                textAlign: "center",
              }}
            >
              {label}
            </div>
          ))}
        </div>
      </Sequence>
    </AbsoluteFill>
  );
};

这段代码做了几件事:

  • Composition 注册了一个 5 秒、30fps、1920x1080 的视频
  • CoreBasics 是真正的画面组件
  • useCurrentFrame() 拿到当前帧号
  • interpolate() 把帧号转换成透明度
  • Sequence from={36} 让卡片从第 36 帧开始出现
  • 数组 map() 生成四张概念卡片

怎么创建项目

新项目可以用官方脚手架创建:

npx create-video@latest

脚手架会让你选择模板。创建完成后进入项目目录,安装依赖并启动 Remotion Studio:

npm install
npm run dev

npm run dev 会打开 Remotion Studio。你可以在左侧选择 Composition,在中间预览画面,在时间轴上拖动帧号。

如果要渲染视频,可以用:

npx remotion render CoreBasics

安装后有哪些关键文件

Remotion 项目看起来像一个 React 项目,但有几个文件最值得先看。

package.json

package.json 里有项目命令和依赖。常见命令包括:

{
  "scripts": {
    "dev": "remotion studio",
    "build": "remotion bundle",
    "lint": "eslint src && tsc"
  }
}

dev 用来启动 Studio。真正渲染视频时,可以使用:

npx remotion render CoreBasics

src/index.ts

src/index.ts 是 Remotion 的入口文件。它会把根组件注册给 Remotion,平时不需要频繁改,但它决定项目从哪里启动。

src/Root.tsx

src/Root.tsx 是新手最应该先看的文件。它决定 Studio 左侧有哪些视频入口,也就是项目里注册了哪些 Composition

画面组件

Composition 只负责注册入口,真正显示画面的代码通常会拆到单独组件里。比如上面的最小示例是 src/CoreBasics.tsx,配套示例里对应的是 src/lessons/CoreBasics.tsx

你会在这些组件里看到 useCurrentFrame()interpolate()Sequence 这类代码。它们负责把当前帧号转换成画面状态。

public/

public/ 放静态素材,例如图片、音频、视频。在 Remotion 组件里可以用 staticFile() 引用这些素材。

remotion.config.ts

remotion.config.ts 是 Remotion 配置文件。它会影响打包、渲染和一些生态能力,比如 Tailwind、Skia 或自定义 webpack 配置。

一开始不需要深入配置文件,但要知道它是项目渲染能力的配置入口。

配套示例怎么看

这组文章有一个可运行的示例项目。第一节对应:

Composition: LessonCoreBasics
文件: src/lessons/CoreBasics.tsx

示例项目为了复用样式,把标题组件、颜色和布局拆到了 src/course/ 目录。所以项目里的文件会比上面的最小示例多一些封装,但核心逻辑是一样的:

Root.tsx 注册 Composition
Composition 指向 React 组件
组件用 frame 计算画面
Remotion 把每一帧合成视频

打开 LessonCoreBasics 后,可以做两个小练习:

  1. 把四张卡片的顺序前后交换。
  2. 把其中一张卡片的颜色换成黄色。

然后在 Studio 里拖动时间轴。只要停在同一帧,画面就应该始终稳定。

下一篇继续讲这个稳定性的来源:为什么 Remotion 动画必须由 frame 决定。

本文标签