remotion-keyframes について
remotion-keyframesは、Remotionでフレームごとに再現可能なアニメーションを作り、Studioで状態を確認するスキルです。被写体の連続性や見えるポーズを重視し、最終フレームまで検証します。
主な機能
- 01現在のフレームからアニメーション値を算出
- 02springとinterpolateによるキーフレーム作成
- 03フレーム遅延による複数要素の順次表示
- 04Studioでタイムライン全体を確認
- 05描画上重要な処理で非決定的な動きを回避
活用シーン
- 01登場、遷移、複数段階のアニメーション制作
- 02単語ごとのテキスト表示やSVG線描画の実装
- 03最初のフレーム、各ポーズ、最終フレームの確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ phamthanhnghia/ remotion-agent-skills --skill remotion-keyframesインストール後、Claude Code で/remotion-keyframesを入力して読み込まれたか確認します。
詳細
Remotion キーフレーム
キーフレームは、目に見える状態、連続した被写体の同一性、フレームごとの決定性、検証済みのピクセルを保証する契約です。
幅広いシーンのレシピには remotion-animation を使用してください。コマンドの全ドキュメントには remotion-cli を使用してください。
手順
- アニメーションする被写体、目に見える状態、最終状態、アニメーションのプリミティブ(
springまたはinterpolate)を特定します。 - プロンプトの要件を証明できる、最小限の仕組みを選びます。
- フレームごとに決定されるキーフレームを作成します。すべての値は
useCurrentFrame()から導出します。 npx remotion studioで検証します。タイムライン全体をスクラブし、最初のフレーム、すべてのポーズ、最終フレームを確認します。- 検証に失敗した場合は、ソースのキーフレームを修正して再検証してから、レンダリングします。
契約
- 動く被写体を明示します。
- 意図した動きを証明するために必要なポーズを、最終状態も含めて明示します。
- 隠れた補助状態ではなく、目に見えるチャンネルをキーフレーム化します。
- 連続性が重要な場合は、オブジェクトの同一性を維持します。
- 読み取り可能な状態や意味のある状態は、認識できるだけの時間保持します(テキストは最低10フレーム、アイコンは最低6フレーム)。
- 最終フレームもアニメーションの一部であり、後処理ではありません。
- 指示がない限り、静止状態に戻さないでください。
- 指示がない限り、黒画面で終わらせないでください。
ランタイムルール
interpolate() と spring():
- すべての値は
frameから同期的に計算します。interpolate/springの呼び出し内で非同期処理を行わないでください。 - 値が意図した範囲外に漏れないよう、
extrapolateLeft: 'clamp'とextrapolateRight: 'clamp'を指定します。 spring({frame, fps, config})—frameには、導出したDate.now()の値ではなく、Remotion のuseCurrentFrame()の値を指定します。- 登場のアニメーションでは、スプリングにオフセットを設定します:
spring({frame: frame - delay, fps, config})。負のフレーム値でスプリングが動作しないよう、Math.max(0, frame - delay)を使用します。
レンダリングに重要な動きには、次を使用しないでください。
Date.now()またはperformance.now()- シードを指定しない
Math.random()(代わりに Remotion のrandom(seed)を使用します) - CSS トランジションまたは CSS アニメーション(シークできません)
requestAnimationFrame- レンダリング処理内の
setTimeout/setInterval - 視覚プロパティの値に対する
useState/useEffect
スプリング設定のリファレンス
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
// Snappy entrance (default feel)
const scale = spring({frame, fps, config: {damping: 200}});
// Bouncy entrance
const bounce = spring({frame, fps, config: {damping: 8, stiffness: 300}});
// Very smooth, slow
const smooth = spring({frame, fps, config: {damping: 30, stiffness: 80, mass: 2}});
// Sharp exit (reverse spring — animate from 1 to 0)
const {durationInFrames} = useVideoConfig();
const exitFrame = durationInFrames - frame;
const exitScale = spring({frame: exitFrame, fps, config: {damping: 200}});
const scaleValue = 1 - (1 - exitScale) ; // goes from 1 down to 0
補間レシピ
import {interpolate} from 'remotion';
// Fade in over 20 frames
const opacity = interpolate(frame, [0, 20], [0, 1], {extrapolateRight: 'clamp'});
// Slide in from left
const x = interpolate(frame, [0, 30], [-300, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Multi-stop color shift
const r = interpolate(frame, [0, 30, 60], [255, 100, 0], {extrapolateRight: 'clamp'});
const g = interpolate(frame, [0, 30, 60], [0, 150, 255], {extrapolateRight: 'clamp'});
// Scale pop
const scale = interpolate(frame, [0, 8, 12, 18], [0, 1.15, 0.95, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
チャンネル
コンポジター/視覚チャンネルを優先します:transform: translateX/Y/Z、transform: scale/rotate/skew、opacity、borderRadius、CSS 変数、clipPath、SVG の strokeDashoffset。
レイアウトチャンネルは避けてください:top/left/right/bottom、width/height、margin/padding。これらはリフローを引き起こして遅く、レンダラーがフレームを並列にサンプリングする際に不正確な結果を生みます。
スタッガーパターン
const ITEM_COUNT = 5;
const STAGGER_FRAMES = 8; // delay between each item
const items = Array.from({length: ITEM_COUNT}, (_, i) => {
const delayedFrame = Math.max(0, frame - i * STAGGER_FRAMES);
const itemSpring = spring({frame: delayedFrame, fps, config: {damping: 200}});
return {opacity: itemSpring, y: (1 - itemSpring) * 40};
});
テキストアニメーションのパターン
const words = 'Hello Remotion World'.split(' ');
const WordReveal = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
return (
<div style={{display: 'flex', gap: 12}}>
{words.map((word, i) => {
const delayedFrame = Math.max(0, frame - i * 6);
const progress = spring({frame: delayedFrame, fps, config: {damping: 200}});
return (
<span
key={i}
style={{
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
display: 'inline-block',
}}
>
{word}
</span>
);
})}
</div>
);
};
SVG のストローク描画
// SVG stroke-dasharray draw-on effect
const pathLength = 500; // measure with path.getTotalLength() in development
const drawProgress = interpolate(frame, [0, 60], [0, 1], {extrapolateRight: 'clamp'});
<path
d="M 0 0 L 300 200"
stroke="white"
strokeWidth={4}
fill="none"
strokeDasharray={pathLength}
strokeDashoffset={pathLength * (1 - drawProgress)}
/>
3D の奥行き(CSS パースペクティブ)
// CSS perspective 3D scene
<div style={{perspective: 800, perspectiveOrigin: '50% 50%'}}>
<div
style={{
transformStyle: 'preserve-3d',
transform: `rotateY(${interpolate(frame, [0, 60], [45, 0], {extrapolateRight: 'clamp'})}deg)`,
}}
>
{/* 3D content */}
</div>
</div>
仕組みの選択
| 必要なもの | 仕組み |
|---|---|
| 自然に減速する登場 | spring() |
| 時間指定の値の遷移 | interpolate() |
| 複数の停止点を含むシーケンス | 複数の入力/出力停止点を指定した interpolate() |
| 順番に登場する多数のアイテム | スタッガー:spring() 内でインデックスに応じて frame - i * delay を指定 |
| テキストの表示(単語ごと) | 単語のインデックスごとにスプリングをスタッガー |
| ストロークが伸びる、または線を描く | interpolate() を使った strokeDashoffset |
| 形状の表示境界 | interpolate() を使った clipPath |
| 3D の奥行きがあるシーン | interpolate() を使った CSS の perspective + rotateX/Y/Z |
エラー処理
| 問題 | 修正方法 |
|---|---|
| 特定のフレームで値が飛ぶ | spring() に負の frame が渡されていないか確認します。Math.max(0, frame - delay) でクランプします。 |
| アニメーションが正しくシークできない | 値を駆動している useState/CSS アニメーションがないことを確認します。 |
| 開始時または終了時に黒いフレームが表示される | 登場時に extrapolateLeft: 'clamp'、終了時に extrapolateRight: 'clamp' が設定されているか確認します。 |
| テキストが点滅する/一貫しない | random() のシードが Math.random() ではなく、安定した文字列であることを確認します。 |
| スプリングが画面外まで行き過ぎる | damping を大きくします(200 = 行き過ぎなし、8 = 大きく弾みます)。 |
完了
Studio で検証します。最初のフレーム、目に見えるすべてのポーズ、最終フレームをスクラブして確認します。被写体に紐づいた動きであること、デバッグ用オーバーレイがないこと、最初のフレームでフラッシュがないことを確認します。
コメント
Markdown 対応