Remotion 大師指南 — React 程式化影片製作
Remotion 讓你能用純 React 程式碼創作出電影般的影片。每個影片畫面都被視為一個確定性的狀態 (frame) => UI,從而產生完美無瑕的 60 FPS 視覺效果,不會有任何卡頓或掉幀。
1. 組合架構與根目錄配置
A. 根入口點 (Root.tsx)
import { Composition } from 'remotion';
import { MainExplainer } from './compositions/MainExplainer';
import { VerticalShort } from './compositions/VerticalShort';
export const RemotionRoot: React.FC = () => {
return (
<>
{/* 16:9 橫向電影解說 */}
<Composition
id="MainExplainer-1080p"
component={MainExplainer}
durationInFrames={30 * 32} // 32 秒 @ 30 FPS = 960 幀
fps={30}
width={1920}
height={1080}
defaultProps={{
title: 'Misteri Mencairnya Puncak Es Dunia',
author: 'NARA Studio',
themeColor: '#f97316',
}}
/>
{/* 9:16 直式 TikTok / Reels / Shorts */}
<Composition
id="VerticalShort-60fps"
component={VerticalShort}
durationInFrames={60 * 15} // 15 秒 @ 60 FPS = 900 幀
fps={60}
width={1080}
height={1920}
/>
</>
);
};
B. 持續時間與 FPS 規則
- 16:9 YouTube / 解說影片:建議使用
30 fps(在渲染速度與運動平滑度之間取得平衡),或為超清晰的鍵擊式文字使用60 fps。 - 幀數計算:一律使用
durationInFrames = Math.round(seconds * fps)計算。 - 零抖動:請勿使用
setInterval或setTimeout。務必使用useCurrentFrame()和useVideoConfig()。
2. 佈局與時間軸基礎元件
AbsoluteFill
所有場景圖層的基礎。自動設定為 position: absolute; inset: 0; width: 100%; height: 100%;:
import { AbsoluteFill } from 'remotion';
export const SceneBackground: React.FC = () => {
return (
<AbsoluteFill style={{ backgroundColor: '#090812', overflow: 'hidden' }}>
{/* 視覺內容 */}
</AbsoluteFill>
);
};
Sequence vs Series
Sequence:將元素放置在絕對時間點(使用from和durationInFrames)。適合背景音樂、浮水印、HUD 疊加層或相互重疊的元素。Series:自動依序排列場景,無需手動計算幀偏移量。
import { Series, Sequence, Audio, staticFile } from 'remotion';
export const VideoStory: React.FC = () => {
return (
<AbsoluteFill>
{/* 整個影片的背景音樂 */}
<Audio src={staticFile('audio/ambient_score.mp3')} volume={0.35} />
{/* 自動場景順序 */}
<Series>
<Series.Sequence durationInFrames={120}>
<HookScene />
</Series.Sequence>
<Series.Sequence durationInFrames={180}>
<ProblemScene />
</Series.Sequence>
<Series.Sequence durationInFrames={240}>
<SolutionScene />
</Series.Sequence>
<Series.Sequence durationInFrames={150}>
<CallToActionScene />
</Series.Sequence>
</Series>
</AbsoluteFill>
);
};
3. 動畫物理:spring() 與 interpolate()
A. 自然彈簧公式 (spring())
使用 spring() 來產生真實的物理運動(消除「PowerPoint」式的僵硬感):
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// 1. 活潑彈出(文字/徽章突然出現)
const popScale = spring({
frame,
fps,
config: { damping: 10, mass: 0.4, stiffness: 180 },
});
// 2. 平滑漂移(鏡頭平滑滑動)
const smoothSlide = spring({
frame,
fps,
config: { damping: 20, mass: 1.2, stiffness: 60 },
});
// 3. 彈力掉落(圖示/粒子掉落並反彈)
const bounce = spring({
frame: frame - 10, // 延遲 10 幀
fps,
config: { damping: 8, mass: 0.6, stiffness: 140 },
});
B. 數值平滑映射 (interpolate())
import { interpolate, Easing } from 'remotion';
// 使用貝茲曲線進行透明度與旋轉過渡
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateRight: 'clamp',
});
const rotateY = interpolate(frame, [0, 60], [-45, 0], {
easing: Easing.bezier(0.16, 1, 0.3, 1),
extrapolateRight: 'clamp',
});
4. 鍵擊式文字與配音同步
A. 逐詞顯示文字
import React from 'react';
import { useCurrentFrame, useVideoConfig, spring, interpolate } from 'remotion';
interface KineticTextProps {
sentence: string;
startFrame?: number;
wordDelay?: number;
}
export const KineticText: React.FC<KineticTextProps> = ({
sentence,
startFrame = 0,
wordDelay = 4, // 每個單詞間隔 4 幀
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const words = sentence.split(' ');
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px', justifyContent: 'center' }}>
{words.map((word, idx) => {
const wordStart = startFrame + idx * wordDelay;
const progress = spring({
frame: frame - wordStart,
fps,
config: { damping: 12, mass: 0.4, stiffness: 160 },
});
const translateY = interpolate(progress, [0, 1], [40, 0]);
const opacity = interpolate(progress, [0, 1], [0, 1]);
return (
<span
key={idx}
style={{
display: 'inline-block',
transform: `translateY(${translateY}px)`,
opacity,
fontSize: '48px',
fontWeight: 800,
color: idx === 0 ? '#f97316' : '#ffffff',
letterSpacing: '-1px',
fontFamily: "'JetBrains Mono', sans-serif",
}}
>
{word}
</span>
);
})}
</div>
);
};
B. 旁白與音效同步
- 將音訊檔案放置在
public/audio/資料夾中。 - 使用
staticFile('audio/file.mp3')引用。 - 使用
volume屬性來建立音訊的淡入/淡出效果:
<Audio
src={staticFile('audio/naskah_vo.mp3')}
volume={(f) =>
interpolate(f, [0, 15, 880, 900], [0, 1, 1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
})
}
/>
5. 非同步資源與預載入 (delayRender)
當影片載入自訂字型、外部圖片或 API 資料時,必須告知 Remotion 以防止渲染出空白幀:
import { delayRender, continueRender } from 'remotion';
import { useEffect, useState } from 'react';
export const DynamicScene: React.FC = () => {
const [handle] = useState(() => delayRender('載入字型與資源'));
useEffect(() => {
document.fonts.ready.then(() => {
continueRender(handle);
});
}, [handle]);
return <AbsoluteFill>...</AbsoluteFill>;
};
6. NARA 動態圖形模板與配方
8 階段電影式公式(告別簡報風)
- 場景 1:開場鉤子 (00-03s) $ ightarrow$ 神秘視覺 + 大型鍵擊式文字。
- 場景 2:問題陳述 (04-07s) $ ightarrow$ 冰層崩塌視覺 / 熾熱的紅色圖表。
- 場景 3:深入數據 (08-12s) $
ightarrow$ 統計數字動態計數上升 (
Math.floor)。 - 場景 4:動態縮放轉換 (13-16s) $ ightarrow$ 從宏觀到微觀的尺度變化。
- 場景 5:人類影響 (17-20s) $ ightarrow$ 卡通角色在畫布上反應。
- 場景 6:解決方案揭示 (21-25s) $ ightarrow$ 視覺轉向明亮的綠色/橙色。
- 場景 7:行動步驟 (26-29s) $ ightarrow$ 3 張 3D 浮動卡片。
- 場景 8:高潮與行動呼籲 (30-32s) $ ightarrow$ 工作室標誌脈衝 + 音效 whoosh 重擊。
7. CLI 渲染執行與硬體加速
A. 渲染單一影片 (1080p 60fps)
npx remotion render src/remotion/index.ts MainExplainer-1080p out/output.mp4 --gl=angle
B. 高並發快速渲染
npx remotion render src/remotion/index.ts MainExplainer-1080p out/output.mp4 --concurrency=8 --gl=angle --jpeg-quality=90
C. 重要渲染標誌
--gl=angle:在 Windows 上使用 DirectX GPU 加速(遠比軟體光柵化快)。--gl=swiftshader:如果在容器/CI 環境中沒有 GPU,則作為軟體備用方案。--concurrency=N:用於平行渲染的 CPU 核心數量。--muted:如果只需要視覺,則不渲染音訊串流。--props='{"title":"新標題"}':透過命令列 JSON 傳遞動態參數。
8. 疑難排解指南
- 黑畫面 / 圖片未顯示:確保資源位於
public/資料夾中,並使用staticFile('檔案名稱')呼叫。 - 渲染時影片卡住:檢查
useMemo中是否有無限迴圈,或 React 元件未正確關閉delayRender。 - 音訊不同步:確保組合的
fps值與旁白秒數的計算完全一致 (seconds = frames / fps)。