Remotion(程式化影片)
Remotion 會逐影格渲染 React 組件。每個影格都是影格編號的純函數,因此動畫是確定性且可重新渲染的。
Remotion 的 API 在不同主要版本之間會有所變動。使用前,請先閱讀已安裝版本(
npm ls remotion)的官方文件(remotion.dev)。使用任何不確定的 API 前,請先查閱 Remotion 的商業使用許可條款。
設定
npx create-video@latest # 選擇空白範本(TypeScript)
cd my-video && npm install
npx remotion studio # 即時預覽
npx remotion render MyComp out/video.mp4
npx remotion still MyComp out/thumb.png --frame=45
思維模型
useCurrentFrame()傳回目前影格,從 0 開始。useVideoConfig()傳回fps、width、height、durationInFrames。- 沒有任何東西會自己動起來。你需要根據影格來計算樣式。
- 絕對不要在動畫中使用 CSS 轉場、
setTimeout、Math.random()或Date.now()。它們會破壞渲染的確定性。請改用 Remotion 的random('seed')。
合成
// src/Root.tsx
import {Composition} from 'remotion';
import {Explainer} from './Explainer';
export const RemotionRoot = () => (
<Composition
id="Explainer"
component={Explainer}
durationInFrames={30 * 20} // 20 秒,30 fps
fps={30}
width={1920}
height={1080}
defaultProps={{title: '你好'}}
/>
);
動畫基本元素
import {AbsoluteFill, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Title: React.FC<{text: string}> = ({text}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const opacity = interpolate(frame, [0, 15], [0, 1], {extrapolateRight: 'clamp'});
const scale = spring({frame, fps, config: {damping: 200}}); // 平滑、無彈跳
return (
<AbsoluteFill style={{justifyContent: 'center', alignItems: 'center'}}>
<h1 style={{opacity, transform: `scale(${0.9 + 0.1 * scale})`, fontSize: 96}}>{text}</h1>
</AbsoluteFill>
);
};
export const Explainer = () => (
<AbsoluteFill style={{backgroundColor: '#0b1020', color: 'white'}}>
<Sequence from={0} durationInFrames={90}><Title text="開場" /></Sequence>
<Sequence from={90} durationInFrames={120}><Title text="重點" /></Sequence>
</AbsoluteFill>
);
除非你需要讓數值超出範圍,否則請務必在 interpolate 中傳入 extrapolateLeft/Right: 'clamp'。
媒體
import {staticFile, Audio, OffthreadVideo, Img} from 'remotion';
<Audio src={staticFile('voiceover.mp3')} />
<Audio src={staticFile('music.mp3')} volume={0.12} />
<OffthreadVideo src={staticFile('demo.mp4')} />
<Img src={staticFile('logo.png')} />
將素材放在 public/ 目錄中,並使用 staticFile() 來引用它們。請使用 <Img> 而非 <img>,這樣渲染程序會等待圖片載入。較新版本的 Remotion 提供了更新的媒體組件;請查閱已安裝版本的說明文件。
將場景與旁白同步
不要硬編碼場景長度。應根據音訊來推導。
- 為每個場景生成獨立的旁白檔案(
voiceover-tts技能)。 - 使用
ffprobe測量每個檔案的長度,並建立scenes.json檔案,內容包含{id, audio, seconds}。 - 在合成中,使用
Math.ceil((seconds + 0.5) * fps)進行轉換,並根據累積偏移量來佈置<Sequence>。 - 在
<Composition>上使用calculateMetadata,根據資料來設定總durationInFrames。
如果重新錄製旁白,影片會自動調整時間。
過場效果
安裝 @remotion/transitions,並使用 TransitionSeries 搭配 fade()、slide()、wipe()。一個過場會重疊其前後兩個場景,因此總長度會減少一個過場的持續時間。在 30 fps 的設定下,過場效果建議維持在 12 到 20 個影格。
字型與品牌
使用 @remotion/google-fonts 或 @remotion/fonts 載入字型,確保它們在第一個影格之前就已準備就緒。將顏色、字型和標誌集中在一個 brand.ts 檔案中,這樣重新品牌化就只需要修改一個檔案。
效能與渲染
- 在工作室中預覽;從命令列渲染。
--concurrency=50%可以限制 CPU 使用率。--codec=h264是預設值;若需後製編輯,請使用--codec=prores。- 大量模糊、陰影和大型 SVG 濾鏡會導致效能緩慢。對於這些效果,建議使用預先渲染的素材。
- 對於原始素材,請使用
<OffthreadVideo>替代<video>,以獲得影格精確的提取。 - 快速草稿:使用
--scale=0.5和--frames=0-90範圍。
常見錯誤
| 錯誤 | 修正方式 |
|---|---|
| 渲染時閃爍或圖片遺失 | 使用 <Img>、staticFile,並為非同步資料加上 delayRender |
| 預覽與渲染的動畫不同 | 移除非確定性的程式碼(時間、隨機數) |
| 在不同語言環境下文字溢出 | 設定明確的寬度;測試最長的字串 |
| 加入過場後影片過長 | 從總影格數中減去過場的影格數 |
| 渲染時音訊無聲 | 檢查檔案路徑,並確認 <Audio> 位於啟用的 <Sequence> 內部 |
建議搭配 motion-design-craft 技能來輔助動畫時間與緩動曲線的設計。