The Own Lab The Own Lab

Remotion 概覽

用 React 寫影片的框架,理解 Composition、Frame、interpolate 核心概念

Overview##

Remotion 是一個用 React 寫影片的框架。它把影片的每一幀視為一次 React render,讓你用元件、props、CSS 這些熟悉的工具來製作影片。

傳統影片製作與 Remotion 的差異:

面向剪輯軟體Remotion
介面時間軸 GUIReact 程式碼
版本控制專案檔難以 diffGit 追蹤每一行變更
參數化手動逐一修改Props 驅動,批次渲染
AI 協作無法自動化LLM 可直接生成程式碼
協作匯出/匯入專案檔Pull Request + Code Review

Note

Remotion 不是要取代 Premiere 或 After Effects。它更適合資料驅動、可重複、需要版本控制的影片場景,例如:產品 demo、社群短影片、個人化行銷影片。

Core Concept##

影片 = 純函數###

Remotion 最核心的設計哲學:

UI = f(frame)

每一幀都是一次獨立的 React render,元件的輸出完全由 frame number 決定。一支 30fps、10 秒的影片 = 300 次 render,每次都是純函數。

這帶來三個關鍵優勢:

  • 確定性 — 給定同一個 frame number,永遠產生相同結果
  • 可跳轉 — 直接預覽第 150 幀,不需要從頭播放
  • 可並行 — 300 幀分給多個 CPU 同時渲染
import { useCurrentFrame, AbsoluteFill } from 'remotion';

export const MyVideo = () => {
  const frame = useCurrentFrame(); // 0, 1, 2, ..., 299

  return (
    <AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center' }}>
      <h1>目前幀數:{frame}</h1>
    </AbsoluteFill>
  );
};

Warning

因為每幀必須是確定性的,Remotion 禁止使用 CSS animation、setTimeoutsetIntervalrequestAnimationFrame。這些都依賴真實時間,會破壞幀的獨立性。

Composition###

Composition 是影片的最小單位——一個 React 元件加上影片 metadata:

import { Composition } from 'remotion';
import { MyVideo } from './MyVideo';

export const RemotionRoot = () => {
  return (
    <Composition
      id="MyVideo"
      component={MyVideo}
      durationInFrames={300} // 總幀數
      fps={30} // 每秒幀數
      width={1920} // 寬度(px)
      height={1080} // 高度(px)
    />
  );
};
屬性說明
id唯一識別碼,渲染時指定用
component影片的 React 元件
durationInFrames總幀數(duration = frames / fps)
fps每秒幀數
width / height影片尺寸(像素)

一個專案可以有多個 Composition,全部在 src/Root.tsx 中註冊:

export const RemotionRoot = () => {
  return (
    <>
      <Composition id="Intro" component={Intro} ... />
      <Composition id="Outro" component={Outro} ... />
    </>
  );
};

Tip

useVideoConfig() 可以在元件內取得 Composition 的 metadata(fps、durationInFrames、width、height),避免硬編碼。

Animation##

interpolate###

interpolate 是一個數學映射函數——把一個數值範圍映射到另一個:

import { useCurrentFrame, interpolate } from 'remotion';

const frame = useCurrentFrame();

// frame 0~30 → opacity 0~1(線性)
const opacity = interpolate(frame, [0, 30], [0, 1]);

// frame 0~60 → x 位移 -100~0
const translateX = interpolate(frame, [0, 60], [-100, 0]);

interpolate 預設允許外推(extrapolation),超出範圍的值會繼續延伸。用 extrapolateRight: 'clamp' 限制在輸出範圍內:

const opacity = interpolate(frame, [0, 30], [0, 1], {
  extrapolateRight: 'clamp', // frame > 30 時,opacity 維持 1
});
選項說明
extrapolateLeft輸入低於範圍時的行為
extrapolateRight輸入超出範圍時的行為
'extend'繼續延伸(預設)
'clamp'固定在邊界值

spring###

spring 模擬物理彈簧運動,產生自然的緩動效果:

import { useCurrentFrame, useVideoConfig, spring } from 'remotion';

const frame = useCurrentFrame();
const { fps } = useVideoConfig();

// 從 0 彈到 1,帶有自然的減速效果
const scale = spring({
  frame,
  fps,
  config: {
    damping: 200, // 阻尼,越大越不彈跳
  },
});

springinterpolate 可以組合使用:

// spring 產生 0→1 的彈簧曲線
const progress = spring({ frame, fps, config: { damping: 15 } });

// 用 interpolate 映射到實際的位移值
const translateY = interpolate(progress, [0, 1], [50, 0]);
const rotation = interpolate(progress, [0, 1], [0, 360]);

Tip

interpolate 是線性映射,spring 是物理模擬。兩者都是純函數——輸入 frame,輸出數值。它們不是動畫引擎,不控制時間軸。

GSAP vs Remotion###

如果你熟悉 GSAP,理解這個差異很重要:

GSAPRemotion
時間驅動真實時間(ms)幀數(frame)
狀態內部 timeline,可變無狀態,純計算
確定性依賴 requestAnimationFrame給定 frame 永遠相同結果
適用場景網頁互動動畫影片輸出(MP4)
// GSAP(命令式):「請在 1 秒內把它變不透明」
gsap.to(el, { opacity: 1, duration: 1 });

// Remotion(宣告式):「在第 N 幀時,透明度是多少?」
const opacity = interpolate(frame, [0, 30], [0, 1]);

Structure##

Sequence###

<Sequence> 控制子元件在哪個時間區段出現:

import { Sequence } from 'remotion';

export const MyVideo = () => {
  return (
    <>
      <Sequence from={0} durationInFrames={60}>
        <Title />
      </Sequence>

      <Sequence from={60} durationInFrames={60}>
        <Content />
      </Sequence>

      <Sequence from={120} durationInFrames={30}>
        <Outro />
      </Sequence>
    </>
  );
};

關鍵特性:<Sequence> 內部的 useCurrentFrame()重置為 0。子元件不需要知道自己在全域時間軸的位置。

Series###

<Series><Sequence> 的語法糖——自動接續,不需要手算 from

import { Series } from 'remotion';

export const MyVideo = () => {
  return (
    <Series>
      <Series.Sequence durationInFrames={60}>
        <Title />
      </Series.Sequence>
      <Series.Sequence durationInFrames={60}>
        <Content />
      </Series.Sequence>
      <Series.Sequence durationInFrames={30}>
        <Outro />
      </Series.Sequence>
    </Series>
  );
};

改任何一段的 durationInFrames,後面的段落自動調整起始時間。

Tip

簡單的順序播放用 <Series>。需要重疊、交叉淡入等複雜排列時用 <Sequence>

Rendering##

Remotion 的渲染流程:

graph LR
  A[React 元件] --> B[逐幀截圖]
  B --> C[PNG 序列]
  C --> D[FFmpeg 編碼]
  D --> E[MP4 / WebM]

三種渲染方式:

方式說明適用場景
Remotion Studio瀏覽器預覽 + GUI 渲染開發階段
CLInpx remotion render本地批次渲染
LambdaAWS Lambda 無伺服器渲染大規模、平行化
# 預覽
npx remotion studio

# 渲染成 MP4
npx remotion render src/index.ts MyVideo out/video.mp4

# 指定 Composition ID
npx remotion render src/index.ts Intro out/intro.mp4

Note

渲染需要安裝 Chromium(Remotion 會自動下載)和 FFmpeg。npx remotion install 可以手動安裝必要的依賴。

Quiz##

Single Choice

為什麼 Remotion 禁止使用 CSS animation 和 setTimeout

Single Choice

一支 24fps、5 秒的影片,React 元件會被 render 幾次?

Single Choice

以下哪個是 interpolate(15, [0, 30], [0, 100]) 的回傳值?

Single Choice

<Sequence> 內部的 useCurrentFrame() 有什麼特殊行為?

Single Choice

<Series> 相比 <Sequence> 的主要優勢是什麼?

Code Challengetypescript

補完這個 Remotion 元件:讓文字在 frame 0~60 之間從完全透明(opacity 0)漸變到完全不透明(opacity 1),並在 frame 60 之後維持不透明。

Ctrl+Enter to run

Summary##

  • Remotion 把影片視為純函數 UI = f(frame),每幀獨立、確定性、可並行
  • Composition 定義影片的元件與 metadata(尺寸、fps、時長)
  • interpolate 做線性數值映射,spring 做物理彈簧模擬,兩者都是純函數
  • 禁止 CSS animation 和 setTimeout——動畫必須由 frame number 驅動
  • <Sequence> 控制時間區段,內部 frame 自動重置;<Series> 自動接續
  • 渲染流程:React render → 逐幀截圖 → FFmpeg 編碼為 MP4

留言 (0)

登入後即可留言