Remotion Compose は何をしますか?
あなたは自然言語による簡潔な概要を、すぐにプレビューできる完全なRemotionコンポジションに変換します。これらは実際のReact/TypeScriptコードであり、フレーム駆動のアニメーションを含みます。出力はsrc/compositions/内の.tsxファイルであり、src/Root.tsxに登録され、ユーザーはStudioですぐにシークバーで操作したり、remotion-renderに渡したりできます。
製品デモ、チュートリアル、機能紹介、マーケティングアニメーションなどの作成、生成、アニメーション化、または「~を表示する20秒の動画コードを書いて」という要求(「Xを表示する20秒の動画を作成」を含む)に対してトリガーされます。
ステップ1 - 概要を抽出する
これらを入手するまではコーディングを開始しないでください。1つのコンパクトなバッチで質問し、明らかなギャップは立ち止まることなく適切なデフォルト値で埋めてください。
- Duration(秒単位)とfps(デフォルトは30 - 製品やマーケティング動画の標準であり、60のレンダリングコストの半分です。60fpsが必要なのは、マウスカーソルの細かい動きやスクロールUIをアニメーション化する場合のみで、30fpsでは目に見えてカクつきます。)
- Scenes - 何が起こるか、順番に。ビートリストを促してください:「ロゴ、次に3つの機能、そしてCTA」は「私のアプリのデモ」よりも優れています。
- Brand - プライマリ/アクセント/背景の16進数カラーと、フォント(
@remotion/google-fontsのフォントファミリー名)。 - Aspect ratio / platform - 16:9 ランドスケープ(1920×1080)、ソーシャル用の9:16 バーティカル(1080×1920)、または1:1 スクエア(1080×1080)。
- Copy - 実際の見出し/機能名/CTAテキスト。製品の主張を決してでっち上げないでください。実際の言葉を尋ねてください。
秒数をフレーム数に変換します:durationInFrames = seconds * fps。
典型的な全長:ソーシャル/製品デモでは15~60秒、広告カットでは6~15秒です。
ステップ2 - シーンプランを作成する
動画ごとに1つの**<Composition>、シーンごとに1つのコンポーネント**を作成し、<Sequence>でタイムライン上に連結します。コードを書く前にフレーム予算をレイアウトします。
合計: 20秒 @ 30fps = 600フレーム
Intro(ロゴ) フレーム 0-90 (Sequence from=0 durationInFrames=90)
機能1 フレーム 90-240 (Sequence from=90 durationInFrames=150)
機能2 フレーム 240-390 (Sequence from=240 durationInFrames=150)
機能3 フレーム 390-540 (Sequence from=390 durationInFrames=150)
CTAアウトロ フレーム 540-600 (Sequence from=540 durationInFrames=60)
予算化の際のペーシングのしきい値:
- テキストを含むビートには、少なくとも2秒(30fpsで60フレーム)を与えます - エンターアニメーションとホールドを含みます。見出しは、読みやすくするために単語あたり約150~200ミリ秒のホールドが必要です。6ワードの見出しは、着地した後に約1秒のホールドが必要です。
- エンターアニメーション:10~20フレーム。クロスフェードとエグジット:12~15フレーム。それ以上長いと緩慢に見え、短いとグリッチのように見えます。
- 意図的なフラッシュカットでない限り、約45フレームより短いシーンはありません。
<Sequence from={F}>は子のローカルクロックをシフトし、そのコンポーネント内部ではuseCurrentFrame()が0から始まります。各シーンを、フレーム0から始まるかのようにアニメーションさせます。
ステップ3 - コアAPIリファレンス
useCurrentFrame()- 現在のフレーム(クロック)。すべてはこれの関数としてアニメーションします。useVideoConfig()-{ fps, width, height, durationInFrames }。interpolate(frame, [inFrames], [outValues], opts)- 線形マッピング。エンター/エグジットでは、値が範囲を超えて動作しないように、常に{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }を渡します。spring({ frame, fps, config })- 物理ベースの0→1(わずかにオーバーシュートします)。生きているように感じさせるもの(ポップ、スライド、スケールイン)に使用します。AbsoluteFill- 全画面の絶対配置div。すべてのシーンのベースレイヤーです。Sequence- 子をタイムシフトおよびタイムバウンドします(シーンスケジューラ)。Series/Series.Sequence-fromオフセットを手動で計算せずに、シーンを連続して配置します。Img,staticFile- ステップ6を参照。
ステップ4 - spring() vs interpolate()
spring()を使う場面 | interpolate()を使う場面 |
|---|---|
| 動作が物理的に感じられるべき場合 - ポップ、バウンス、スケールイン、スライドイン | 正確なフレーム間で正確でリニアな制御が必要な場合 |
| 自然な落ち着きやオーバーシュートが必要な場合 | フェード(不透明度0→1)、色の変化、プログレスバー、フレームに合わせたタイミングが必要なもの |
| カード、ロゴ、バッジのエントランス | クロスフェードやエグジットで、オーバーシュートが見た目に悪い場合 |
パターン - トランスフォームをspringで、不透明度をinterpolateで駆動します:
const { fps } = useVideoConfig();
const frame = useCurrentFrame();
const pop = spring({ frame, fps, config: { damping: 200 } }); // 0 → 1, 生き生きと
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
const scale = interpolate(pop, [0, 1], [0.8, 1]);
damping: 200は主力の設定です - ほとんど目立たないオーバーシュートで素早く落ち着きます。より低いダンピング(~10~20)は視覚的にバウンスします。バウンスが目的の場合のみ使用してください。
ステップ5 - 標準的な製品デモ構造
特に指定がない限り、この形状をエンコードします:ロゴ/ブランドイントロ → N個の機能コールアウト → CTAアウトロ。 データ駆動型(ステップ6)にすることで、バリエーションを自由に変更できます。
// src/compositions/ProductDemo.tsx
import {
AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig,
interpolate, spring,
} from "remotion";
import { loadFont } from "@remotion/google-fonts/Inter";
const { fontFamily } = loadFont();
export type Feature = { title: string; body: string };
export type ProductDemoProps = {
brand: string;
colors: { bg: string; fg: string; accent: string };
logoText: string;
features: Feature[];
cta: string;
};
const Intro: React.FC<{ text: string; colors: ProductDemoProps["colors"] }> = ({ text, colors }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const s = spring({ frame, fps, config: { damping: 200 } });
const scale = interpolate(s, [0, 1], [0.7, 1]);
const opacity = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.bg, justifyContent: "center", alignItems: "center" }}>
<h1 style={{ fontFamily, fontSize: 120, color: colors.fg, transform: `scale(${scale})`, opacity }}>
{text}
</h1>
</AbsoluteFill>
);
};
const FeatureCallout: React.FC<{ feature: Feature; colors: ProductDemoProps["colors"] }> = ({ feature, colors }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const slide = spring({ frame, fps, config: { damping: 200 } });
const x = interpolate(slide, [0, 1], [-60, 0]);
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.bg, justifyContent: "center", paddingLeft: 160 }}>
<div style={{ transform: `translateX(${x}px)`, opacity }}>
<div style={{ fontFamily, fontSize: 32, color: colors.accent, fontWeight: 700, letterSpacing: 2 }}>
{feature.title.toUpperCase()}
</div>
<div style={{ fontFamily, fontSize: 64, color: colors.fg, maxWidth: 1100, marginTop: 16 }}>
{feature.body}
</div>
</div>
</AbsoluteFill>
);
};
const Outro: React.FC<{ cta: string; colors: ProductDemoProps["colors"] }> = ({ cta, colors }) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 18], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.accent, justifyContent: "center", alignItems: "center" }}>
<h2 style={{ fontFamily, fontSize: 84, color: colors.bg, opacity }}>{cta}</h2>
</AbsoluteFill>
);
};
export const ProductDemo: React.FC<ProductDemoProps> = ({ colors, logoText, features, cta }) => {
const intro = 90;
const per = 150;
const outro = 60;
return (
<AbsoluteFill>
<Sequence durationInFrames={intro}>
<Intro text={logoText} colors={colors} />
</Sequence>
{features.map((f, i) => (
<Sequence key={i} from={intro + i * per} durationInFrames={per}>
<FeatureCallout feature={f} colors={colors} />
</Sequence>
))}
<Sequence from={intro + features.length * per} durationInFrames={outro}>
<Outro cta={cta} colors={colors} />
</Sequence>
</AbsoluteFill>
);
};
それをsrc/Root.tsxに登録し、同じ数値からdurationInFramesを計算し、defaultPropsを指定します:
<Composition
id="ProductDemo"
component={ProductDemo}
durationInFrames={90 + 3 * 150 + 60}
fps={30}
width={1920}
height={1080}
defaultProps={{
brand: "Acme",
colors: { bg: "#0B0B0F", fg: "#FFFFFF", accent: "#6C5CE7" },
logoText: "Acme",
features: [
{ title: "高速", body: "スプリントではなく秒単位で出荷。" },
{ title: "シンプル", body: "1つのプロンプト、1つの動画。" },
{ title: "あなたのもの", body: "あなたのブランド、あなたのコピー。" },
],
cta: "無料で始める →",
}}
/>
バーティカル/ソーシャル用には、width={1080} height={1920}を設定します。スクエア用には1080×1080です。コンポーネントは同一に保ちます - キャンバスのみが変更されます。
ステップ6 - データ駆動型プロパティと静的アセット
上記のコンポジションは完全にデータ駆動型です。すべての単語と色はプロパティです。これがポイントです - バリエーション(新しいコピー、新しいパレット、ローカライズされたテキスト)は再プロンプトが不要で、異なるプロパティを渡すだけです(remotion-renderはバッチ処理のためにJSONファイルからこれを行います)。
静的アセット(ロゴ、スクリーンショット、オーディオ)はpublic/assets/に配置し、staticFile()で参照します - インポートや生のパスは使用しません:
import { Img, staticFile } from "remotion";
<Img src={staticFile("assets/logo.png")} />
ステップ7 - 出力
完成したファイルをsrc/compositions/<VideoName>.tsxに保存し、src/Root.tsxに登録し、npx remotion studio(http://localhost:3000)でプレビューするようにユーザーに伝えます。その後、引き継ぎます:*「Studioでシークバーを操作してください。変更点(速すぎる、色が間違っている、別のコピーが必要など)があれば教えてください。編集して再レンダリング(`remotion-render`)します。」*
やってはいけないこと
- CSSトランジション/アニメーションや
setTimeout/setIntervalでアニメーションさせてはいけません。Remotionは各フレームを独立して並行にレンダリングします。useCurrentFrame()から派生していないものは、ブラウザプレビューでは正しく見えても、最終レンダリングでは見えないか、一貫性がありません。 - 散乱/パーティクル効果に
Math.random()を使用してはいけません。すべてのレンダラワーカーが異なる値を取得し、フレームが一致しなくなります。決定論的な疑似ランダム性には、Remotionのrandom(seed)を使用してください。 delayRender()/continueRender()なしでデータをフェッチしたり、非同期処理を待機したりしてはいけません。レンダラーはデータが到着する前にフレームをキャプチャします。- シーンコンポーネント内でfps、width、heightをハードコードしてはいけません。
useVideoConfig()から読み取ることで、30↔60fpsの切り替えやランドスケープ↔バーティカルの変更がすべてのタイミングとレイアウトを壊さないようにします。 - エンター/エグジットの補間で
extrapolateLeft/Right: "clamp"を省略してはいけません。不透明度が1を超えて上昇したり、要素が範囲終了後にキャンバス外にドリフトしたりします。 - 見出し、機能の主張、CTAをでっち上げてはいけません。ユーザーの実際のコピーを使用し、欠落している場合はそれを尋ねてください。
品質基準
- すべてのアニメーション値は、
useCurrentFrame()(とプロパティ)の純粋な関数です。Studioで逆方向にシークバーを操作しても、前方に再生するのと同一に見えます。 - プランのフレーム予算が
Root.tsxのdurationInFramesと正確に一致し、テキストビートが読み取り可能な最小時間未満で保持されることはありません。 - すべてのコピーと色は、
defaultPropsが指定された型付きプロパティを介して届きます。シーンコンポーネントにリテラルが埋め込まれていません。 - アセットは
staticFile()を介して読み込まれます。ファイルはクリーンに登録され、Studioでコンソールエラーなしでプレビューされます。