Communitygithub.com

ahmadzulfi/video

Remotion のマークアップを構造化し、インタラクティブにします。

video とは?

このスキルは、Remotion Studio のインスペクターがコード構造を認識し、クリック選択、ドラッグ&ドロップ、リサイズ、回転、CSS スタイルの編集、キーフレームやイージング値の編集といったインタラクティブ機能を有効にするための、Remotion マークアップの推奨パターンを文書化したものです。Claude Code や Cursor などの AI エージェントが、ユーザーが Remotion コンポーネントを記述する際に、これらのパターンに従うように支援するために使用されます。その有用性は、スタジオ内での編集性を最大化し、バグを減らし、タイムライン管理を改善することにあります。具体的には、`Interactive.withSchema()` を使用してスキーマ定義付きのコンポーネントを作成する方法、個々の編集可能アイテムに独立した JSX ノードを割り当てる方法、インライン CSS と `interpolate()` を用いたアニメーション、そして接続されたコンポジションとしてのコンポーネント登録などのベストプラクティスをカバーしています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ahmadzulfi/video/tree/HEAD/.agents/skills/remotion-interactivity

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

プレビュー

スキル README より

video preview

ドキュメント

Remotion のマークアップを特定の方法で記述することで、Remotion Studio がコードの構造を認識し、インタラクティブにすることができます。

  • アイテムをクリックして選択できるようにする
  • ドラッグ&ドロップ、リサイズ、回転を可能にする
  • CSS スタイルを編集できるようにする
  • キーフレームとイージング値を編集可能にする

マークアップが Studio にとって複雑すぎてインタラクティブにできない場合、値はグレーアウトされます。

独自のタイムラインを持つインタラクティブコンポーネントを優先する

親要素を埋めるコンポーネントには Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'}) を使用します。ラッパーは視覚的なルートとタイムラインアイテムを提供し、デフォルトでオーバーフローをクリップするため、内部コンポーネントはフラグメントを返すことができます。タイミング、プリマウント、寸法、およびインスタンスごとのスタイルは、エクスポートされたコンポーネントに指定します。

一緒に属するビジュアルは同じコンポーネントにまとめると、一緒に移動およびトリミングされます。内部のレイアウトコンテナは、小さな領域内での配置、クリッピング、またはアニメーショングループを提供できます。シーン全体を埋めるだけのコンテナは通常不要です。カード、テキストボックス、独自の境界を持つ SVG は、デフォルトの layout: 'none' を維持し、style を視覚的なルートに転送できます。

import {Video} from '@remotion/media';
import type React from 'react';
import {Interactive, useVideoConfig, type InteractivitySchema} from 'remotion';

type SceneProps = {
  readonly title: string;
};

const SceneInner: React.FC<SceneProps> = ({title}) => {
  const {fps} = useVideoConfig();

  return (
    <>
      <Video
        name="Background footage"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{width: '100%', height: '100%'}}
      />
      <Interactive.Div
        name="Scene title"
        style={{
          position: 'absolute',
          left: 80,
          bottom: 80,
          color: 'white',
          fontFamily: 'Helvetica, Arial, sans-serif',
          fontSize: 80,
        }}
      >
        {title}
      </Interactive.Div>
    </>
  );
};

const sceneSchema = {
  title: {type: 'string', default: '', description: 'Title'},
} as const satisfies InteractivitySchema;

export const Scene = Interactive.withSchema({
  Component: SceneInner,
  componentName: 'Scene',
  schema: sceneSchema,
  wrapInSequence: true,
  layout: 'absolute-fill',
});

スキーマフィールドを選択する

スキーマの各キーは、Studio が呼び出し元で読み取りおよび編集できるプロパティです。 スキーマに含まれていないプロパティも機能しますが、Studio では編集できません。 キーにはドット記法を使用できます(例:style.color)。

タイプ用途キーフレーム可能
stringタイトル、プロンプト、コードスニペットなどの文字列いいえ
number数値。オプションで min、max、step、integer を指定可能はい
booleanオン/オフスイッチはい(ホールド)
colorCSS カラー文字列はい
enumvariants 間の選択。各バリアントは独自のネストされたスキーマを持つオプトイン(ホールド)
array数値、色、列挙型などのリストいいえ
assetメディアソース。オプションで assetType を指定可能いいえ
font-familyCSS フォントファミリーいいえ
font-weightフォントの太さはい
translate、scale、rotation-css、rotation-degrees、transform-originトランスフォームはい
uv-coordinate要素上の正規化された [x, y] 点はい
svg-pathSVG パスデータはい
remotion-captionsCaption[] データいいえ
hiddenコントロールから除外される値—

すべてのフィールドには default が必要で、description を持つべきです。Studio はこれをラベルとして表示します。 keyframable: false を設定すると、静的なコントロールが表示されます。

コンテンツとインスタンスごとの外観(テキストやアクセントカラーなど)を公開します。 style.translate、style.scale、style.rotate、style.transformOrigin、style.opacity のフィールドは追加しないでください。wrapInSequence: true によって既に追加されています。

ルートの CSS プロパティをインスタンスごとに編集可能にするには、組み込みのスキーマフラグメントを展開します。

const cardSchema = {
  ...Interactive.textSchema, // style.color, style.fontSize, style.fontWeight, ...
  ...Interactive.backgroundSchema, // style.backgroundColor
  ...Interactive.borderRadiusSchema, // style.borderRadius, ...
} as const satisfies InteractivitySchema;

Interactive.borderSchema および Interactive.captionsSchema(キャプションを受け入れるコンポーネント用)も利用可能です。 React.ReactNode の子を受け入れるコンポーネントには、...Interactive.childrenSchema を追加します。 これは子コンテンツのコントロールを提供し、将来的によりリッチなコンテンツをサポートするように進化する可能性があります。 現在は、デフォルトが '' の "Text" というラベルの文字列フィールドを使用します。 コンポーネントが文字列専用の子を必要とする場合は、代わりに type: 'string' を持つ明示的な children フィールドを宣言してください。 Interactive.textSchema はタイポグラフィスタイルを制御し、childrenSchema はコンテンツを制御します。 children フラグメントは baseSchema に含まれておらず、カスタムコンポーネントに自動的に追加されることもありません。 ネストされたマークアップと計算された子は現在読み取り専用です。 text-content は string フィールドタイプの非推奨のエイリアスです。 すべてのオプションについては InteractivitySchema を参照してください。

再利用可能なコンポーネントを接続されたコンポジションとして登録する

重要なシーンや再利用可能なコンポーネントを、独自のレイヤーまたはアニメーションを持つスタンドアロンのコンポジションとして登録します。 Studio はこれらを接続されたコンポジションと呼びます。親ビデオと同じコンポーネント実装を共有しながら、独自のタイムラインで開くことができます。

親なしで機能するように、コンポーネント内で必要なスタイルを定義し、フォントをロードします。 親の独立性を参照してください。

親がレンダリングするのと同じエクスポートされたコンポーネント参照を登録します。上記の例では、SceneInner やインラインラッパーではなく、component={Scene} を使用します。 wrapInSequence: true の場合、接続のために追加の <Sequence> は必要ありません。

import {Composition} from 'remotion';
import {Scene} from './Scene';

export const RemotionRoot = () => (
  <Composition
    id="Scene"
    component={Scene}
    width={1280}
    height={720}
    fps={30}
    durationInFrames={30}
    defaultProps={{
      title: 'A new perspective',
    }}
  />
);

コンポーネントのプレビューに適した、一意の ID、代表的なインラインのデフォルト値、寸法、fps、および期間を選択します。 登録のデフォルト値は、親インスタンスからプロパティを自動的にコピーしません。 登録と抽出の詳細については、接続されたコンポジションを参照してください。

タイミングをコンポーネントに直接配置する

すでにタイミングプロパティを受け入れている 1 つのコンポーネントの周囲に、別の <Sequence> を配置することは避けてください。 組み込みのインタラクティブコンポーネントおよび wrapInSequence: true で作成されたカスタムコンポーネントは、name、from、durationInFrames、trimBefore、playbackRate、premountFor を直接受け取ることができます。

<Scene
  name="Opening scene"
  from={30}
  durationInFrames={90}
  premountFor={fps}
  style={{opacity: 0.9}}
  title="A new perspective"
/>

冗長なシーケンスを削除するときは、そのタイミングと名前を子に移動します。 既存の子のタイミングは保持し、オフセットを上書きまたは二重適用しないでください。 コンポーネント内の useCurrentFrame() 呼び出しはローカルフレームを読み取りますが、呼び出し元のスタイル式は呼び出し元のフレームを使用します。アニメーションスタイルを移動するときは、この区別を維持してください。

<Sequence> は、複数の兄弟に共有タイミングを提供する場合、タイミングを処理しないコンポーネントをラップする場合、または子が直接受け入れられない寸法を提供する場合に保持します。 管理されたルートは useVideoConfig() の width と height も受け入れます。連続したレイアウトには <TransitionSeries.Sequence> を保持します(トランジションの有無は問いません)。直接の from プロパティはこれらの動作を置き換えません。

個別に編集可能な各アイテムに独自の JSX ノードを割り当てる

Studio はアイテムを作成した JSX ソースノードを編集します。複数のランタイムアイテムが同じ JSX ノードから生成される場合、それらは 1 つのソース編集ターゲットを共有します。

個別に編集可能であるべきすべてのコンポジション登録、クリップ、シーン、レイヤー、シーケンスに対して、別々の JSX ノードを作成し、編集可能なプロパティをそのノードに保持します。これは <Composition>、<Still>、組み込みメディアコンポーネント、<Sequence>、<TransitionSeries.Sequence>、およびカスタムコンポーネントに適用されます。

例えば、以下のように編集可能なタイムラインを作成します。

import { TransitionSeries } from "@remotion/transitions";

<TransitionSeries>
  <TransitionSeries.Sequence name="Introduction" durationInFrames={90}>
    <Introduction />
  </TransitionSeries.Sequence>
  <TransitionSeries.Sequence name="Demo" durationInFrames={150}>
    <Demo />
  </TransitionSeries.Sequence>
</TransitionSeries>

.map() やその他のプログラムによるループは、1 つの JSX ソースノードから複数のランタイムアイテムを作成します。これは、可視化バーやパーティクルなど、意図的に 1 つのテンプレートとして制御される繰り返し出力に適しています。インスタンスが Studio 内で独立した ID や名前、プロパティ、メタデータ、タイミング、順序、削除、複製を必要とする場合には適していません。

Interactive を使用して HTML 要素をインタラクティブにする

<Img>(すでにインタラクティブです)を除くすべての HTML および SVG 要素(<div> など)は、Interactive を使用してインタラクティブにできます。

<Interactive.Div
  name="Greeting card"
  style={{fontSize: 80, padding: 24}}
>
  Hello
</Interactive.Div>

これにより、Studio でスタイルとキーフレームを設定できます。常識的に判断してください。コンポーネントに多くの要素がある場合、タイムラインが乱雑になる可能性があります。

インラインテキストを優先する

テキストが固定されており、1 回だけ使用される場合は、定数に抽出する代わりに、インタラクティブ要素内に直接記述します。

// 👍 固定コピーは編集可能なまま
<Interactive.Div name="Title">
  Remotion Best Practices
</Interactive.Div>

テキストが動的であるか再利用される場合にのみ、プロパティまたは変数を使用します。

インタラクティブ要素に説明的な名前を付ける

要素に name プロパティを追加して、簡単に識別できるようにします。 計算された名前は避け、ハードコードします。

<>
  <Interactive.Div name="Hero title" style={{fontSize: 80}}>
    Launch day
  </Interactive.Div>
  <Img name="Avatar" src="https://remotion.media/image.jpeg" />
  <Video name="Background" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="Title">
    Launch day
  </Interactive.Div>
</>

すべての CSS スタイルをインラインに保つ

最善の方法は、style にプレーンなオブジェクトを渡すことです。定数を参照したり、オブジェクトをスプレッドしたり、計算を含めたりしないでください。

<Interactive.Div
  style={{
    fontSize: 80,
    color: 'red',
  }}
>
  Hello World!
</Interactive.Div>
const baseStyle = useMemo(() => {
  return {
    fontSize: 12 // ❌ インラインでないスタイルはサポートされていません
  }
}, []);

<Interactive.Div
  style={{
    ...baseStyle, // ❌ スプレッドはサポートされていません
    color: RED, // ❌ 定数の参照はサポートされていません
    scale: frame * 10 // ❌ 計算はサポートされていません
  }}
>
  Hello World!
</Interactive.Div>

interpolate() を使用してアニメーション化する

アニメーションは、変化するプロパティに対するインラインの interpolate() 呼び出しとして記述します。 出力範囲、イージング、外挿、output プロパティはハードコードされた値を使用する必要があります。

入力範囲は、useVideoConfig() から直接分割代入された durationInFrames、fps、width、height を追加で使用できます。durationInFrames のような単なる識別子、2 * fps や fps * 2 のような数値との乗算、durationInFrames - 1 のような数値の減算がサポートされています。

const {fps, durationInFrames} = useVideoConfig();

// 👍 インライン値は標準化およびキーフレーム化可能
<Interactive.Div
  name="Product card"
  style={{
    color: 'white',
    fontSize: 80,
    scale: interpolate(frame, [0, fps], [0, 1], {
      easing: Easing.spring({damping: 200}),
      output: 'perceptual-scale',
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp'
    }),
    rotate: interpolate(frame, [0, 1 * fps], ['0deg', '20deg'], {
      easing: Easing.spring({damping: 200}),
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp'
    }),
    translate: interpolate(
      frame,
      [durationInFrames - 30, durationInFrames],
      ['0px 0px', '0px 120px'],
      {
        easing: Easing.spring({damping: 200}),
        extrapolateLeft: 'clamp',
        extrapolateRight: 'clamp'
      }
    ),
  }}
>
  Hello World!
</Interactive.Div>

output を省略するとデフォルトで "number" になり、scale には適していますが translate や rotate には適していません。これらのプロパティには、output 引数を使用して適切なユニットを設定します。 extrapolateLeft と extrapolateRight は必須であり、"clamp"、"extend"、または "identity" に設定できます。

interpolate() を使用する代わりに、CSS キーフレームアニメーションや <TransitionSeries> を使用しても、インスペクターが簡単に解釈できる限り構いません。

インタラクティブコンポーネントと CSS ルール

Interactive コンポーネントはインラインスタイルのみをサポートします。CSS-in-JS、CSS モジュール、またはスタイルシートはサポートしていません。

コンポーネントファイル内の useStyles()、makeStyles()、または CSS モジュールのインポートをすべて削除します。

これらのルールは size、fill、stroke などの SVG 属性には適用されません。これらはインラインの HTML 属性として直接記述できます。

インタラクティブコンポーネントとテキスト

インタラクティブコンポーネントは、それらのコンポーネントがテキストをレンダリングするために子を使用することを期待しません。子には常にテキストを入力してください。子がない場合、Studio はプレースホルダーテキストを表示します。

インスペクターに表示されるもの

スタジオは、コンポーネントのプロパティを操作するためのコントロールを自動的に生成します。

  • Interactive.withSchema() を使用すると、すべてのプロパティを一度に表示できます。
  • スキーマ内の各フィールドに対してコントロールが生成されます。
  • スキーマに default 値がないフィールドは無視されます。
  • description はコントロールのラベルとして使用されます。
  • コントロールのタイプは、type プロパティによって決定されます。

コンポーネントの独立性

これらのプラクティスに従うことで、コンポーネントは自己完結型になり、親のコンテキストに依存しなくなります。これにより、コンポーネントは接続されたコンポジションとして登録でき、Studio 内での再利用性と編集性が向上します。

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

関連スキル