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: '世界の氷河頂上融解の謎',
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. フレームレートと尺のルール
- 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)。BGM、透かし、HUDオーバーレイ、重なり合う要素に最適です。Series: シーンを自動的に順次配置するため、手動でフレームオフセットを計算する必要がありません。
import { Series, Sequence, Audio, staticFile } from 'remotion';
export const VideoStory: React.FC = () => {
return (
<AbsoluteFill>
{/* 動画全体を通してのBGM */}
<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, // 単語間のフレーム間隔
}) => {
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ステージのシネマティック公式(アンチPPT)
- シーン 1: コールドフック (00-03秒) → 謎めいたビジュアル + 大きなキネティックタイポグラフィ。
- シーン 2: 問題提起 (04-07秒) → 氷河崩壊のビジュアル / 燃えるような赤いグラフ。
- シーン 3: データ深掘り (08-12秒) → 統計数字がカウントアップ (
Math.floor)。 - シーン 4: ダイナミックスケールシフト (13-16秒) → マクロからミクロへのスケール変化。
- シーン 5: 人間への影響 (17-20秒) → キャンバス上のキャラクターの反応。
- シーン 6: 解決策の提示 (21-25秒) → ビジュアルが明るい緑/オレンジに切り替わる。
- シーン 7: アクションステップ (26-29秒) → 3つのポイントが浮かぶ3Dカード。
- シーン 8: クライマックスとCTA (30-32秒) → スタジオロゴのパルス + ウィッシュサウンド効果。
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内の無限ループや、delayRenderを閉じていない React コンポーネントがないか確認してください。 - オーディオの同期がずれる: コンポジションの
fps値が、ボイスオーバーの秒数計算(seconds = frames / fps)と完全に一致していることを確認してください。