Communitygithub.com

hiwurun/remotion-saas-boilerplate

Remotion-Markup für Interaktivität strukturieren

Was ist remotion-saas-boilerplate?

Dieses Skill beschreibt, wie Remotion-Markup strukturiert werden muss, damit der Remotion Studio die Komponenten interaktiv machen kann – inklusive Auswahl per Klick, Drag & Drop, Größenänderung, Rotation sowie Bearbeitung von CSS-Stilen und Keyframes. Der Fokus liegt auf der Verwendung von `Interactive.withSchema()` für Komponenten mit eigenen Timelines, der Wahl passender Schema-Felder (z. B. String, Number, Color, Asset) und der Registrierung wiederverwendbarer Komponenten als „Connected Compositions“. Es wird empfohlen, Timing-Props direkt auf der Komponente zu setzen, anstatt separate `<Sequence>`-Wrapper zu verwenden, und für jedes unabhängig editierbare Element einen eigenen JSX-Knoten zu schreiben. Das Skill ist plattformunabhängig und richtet sich an Entwickler, die mit Remotion Studio arbeiten und ihre Komponenten interaktiv gestalten möchten. Es enthält konkrete Codebeispiele, Richtlinien für inline Styles und Animationen mit `interpolate()` sowie Hinweise zu Schema-Fragmenten wie `Interactive.textSchema` und `Interactive.backgroundSchema`.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/hiwurun/remotion-saas-boilerplate/tree/HEAD/.agents/skills/remotion-interactivity

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Vorschau

Aus dem Skill-README

remotion-saas-boilerplate preview

Dokumentation

Durch die spezifische Strukturierung von Remotion-Markup kann der Remotion Studio die Struktur des Codes erkennen und interaktiv machen:

  • Auswählen von Elementen durch Klicken
  • Drag & Drop, Größenänderung und Rotation
  • Bearbeiten von CSS-Stilen
  • Bearbeiten von Keyframes und Easing-Werten

Wenn das Markup für den Studio zu komplex ist, um es interaktiv zu machen, werden die Werte ausgegraut.

Interaktive Komponenten mit eigenen Timelines bevorzugen

Verwenden Sie Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'}) für Komponenten, die ihr übergeordnetes Element ausfüllen. Der Wrapper bietet ihre visuelle Wurzel und das Timeline-Element und schneidet standardmäßig Überläufe ab, sodass die innere Komponente ein Fragment zurückgeben kann. Setzen Sie Timing, Vorladezeit, Abmessungen und instanzspezifische Stile auf die exportierte Komponente.

Halten Sie visuelle Elemente, die zusammengehören, in derselben Komponente, damit sie sich gemeinsam bewegen und trimmen lassen. Ein innerer Layout-Container kann dennoch Ausrichtung, Beschneidung innerhalb eines kleineren Bereichs oder eine animierte Gruppe bieten; ein Container, der nur die Szene ausfüllt, ist normalerweise unnötig. Karten, Textfelder und SVGs mit eigenen Grenzen können den Standardwert layout: 'none' behalten und style an ihre visuelle Wurzel weiterleiten.

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="Hintergrundmaterial"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{width: '100%', height: '100%'}}
      />
      <Interactive.Div
        name="Szenentitel"
        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: 'Titel'},
} as const satisfies InteractivitySchema;

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

Schema-Felder auswählen

Jeder Schlüssel im Schema ist eine Eigenschaft, die der Studio an der Aufrufstelle lesen und bearbeiten kann. Eigenschaften, die nicht im Schema sind, funktionieren weiterhin, sind aber im Studio nicht bearbeitbar. Schlüssel können Punktnotation verwenden, wie z. B. style.color.

TypVerwendung fürKeyframable
stringZeichenketten, z. B. Titel, Prompt oder Code-SnippetNein
numberZahlen, mit optionalen min, max, step, integerJa
booleanEin/Aus-SchalterJa (halten)
colorCSS-FarbstringsJa
enumAuswahl zwischen variants, jede mit eigenem verschachtelten SchemaJa (halten)
arrayListen von Zahlen, Farben, Enums und mehrNein
assetMedienquellen, mit optionalem assetTypeNein
font-familyCSS-SchriftfamilieNein
font-weightSchriftstärkeJa
translate, scale, rotation-css, rotation-degrees, transform-originTransformationenJa
uv-coordinateEin normalisierter [x, y]-Punkt auf dem ElementJa
svg-pathSVG-PfaddatenJa
remotion-captionsCaption[]-DatenNein
hiddenEin Wert, der aus den Steuerelementen herausgehalten wird—

Jedes Feld benötigt einen Standardwert (default) und sollte eine Beschreibung (description) haben, die der Studio als Label anzeigt. Setzen Sie keyframable: false, um ein statisches Steuerelement anzuzeigen.

Geben Sie den Inhalt und das instanzspezifische Erscheinungsbild frei, z. B. den Text und eine Akzentfarbe. Fügen Sie keine Felder für style.translate, style.scale, style.rotate, style.transformOrigin und style.opacity hinzu: wrapInSequence: true fügt sie bereits hinzu.

Um CSS-Eigenschaften der Wurzel pro Instanz bearbeitbar zu machen, verteilen Sie die integrierten Schema-Fragmente:

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

Ebenfalls verfügbar: Interactive.borderSchema und Interactive.captionsSchema für Komponenten, die Untertitel akzeptieren. Fügen Sie für Komponenten, die React.ReactNode-Children akzeptieren, ...Interactive.childrenSchema hinzu. Es bietet Steuerelemente für den Inhalt von Children und kann sich weiterentwickeln, um umfangreichere Inhalte zu unterstützen. Derzeit wird ein String-Feld mit der Bezeichnung "Text" und einem Standardwert von '' verwendet. Wenn eine Komponente ausschließlich String-Children erfordert, deklarieren Sie stattdessen ein explizites children-Feld mit type: 'string'. Interactive.textSchema steuert Typografiestile; childrenSchema steuert den Inhalt. Das Children-Fragment ist nicht in baseSchema enthalten und wird nicht automatisch zu benutzerdefinierten Komponenten hinzugefügt. Verschachteltes Markup und berechnete Children sind derzeit schreibgeschützt. text-content ist ein veralteter Alias für den Feldtyp string. Siehe InteractivitySchema für alle Optionen.

Wiederverwendbare Komponenten als verbundene Kompositionen registrieren

Registrieren Sie wesentliche Szenen und wiederverwendbare Komponenten mit eigenen Ebenen oder Animationen als eigenständige Kompositionen. Der Studio nennt diese verbundene Kompositionen: Sie können in einer eigenen Timeline geöffnet werden, während sie dieselbe Komponentenimplementierung mit dem übergeordneten Video teilen.

Definieren Sie erforderliche Stile und laden Sie Schriftarten innerhalb der Komponente, damit sie ohne ihr übergeordnetes Element funktioniert. Siehe Elternunabhängigkeit.

Registrieren Sie dieselbe exportierte Komponentenreferenz, die das übergeordnete Element rendert. Für das obige Beispiel verwenden Sie component={Scene}, nicht SceneInner oder einen Inline-Wrapper. Mit wrapInSequence: true ist kein zusätzliches <Sequence> für die Verbindung erforderlich.

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: 'Eine neue Perspektive',
    }}
  />
);

Wählen Sie eine eindeutige ID, repräsentative Inline-Standardwerte sowie Abmessungen, fps und Dauer, die für die Vorschau der Komponente geeignet sind. Registrierungsstandards kopieren nicht automatisch Eigenschaften von einer übergeordneten Instanz. Siehe verbundene Kompositionen für Details zur Registrierung und Extraktion.

Timing direkt auf der Komponente setzen

Vermeiden Sie einen separaten <Sequence> um eine Komponente, die bereits Timing-Eigenschaften akzeptiert. Integrierte interaktive Komponenten und benutzerdefinierte Komponenten, die mit wrapInSequence: true erstellt wurden, können name, from, durationInFrames, trimBefore, playbackRate und premountFor direkt empfangen.

<Scene
  name="Eröffnungsszene"
  from={30}
  durationInFrames={90}
  premountFor={fps}
  style={{opacity: 0.9}}
  title="Eine neue Perspektive"
/>

Wenn Sie eine redundante Sequenz entfernen, verschieben Sie deren Timing und Namen in das Kind. Bewahren Sie vorhandenes Timing des Kinds; überschreiben oder doppeln Sie Offsets nicht. Ein useCurrentFrame()-Aufruf innerhalb der Komponente liest ihren lokalen Frame, während ein Style-Ausdruck an der Aufrufstelle den Frame des Aufrufers verwendet. Bewahren Sie diese Unterscheidung beim Verschieben von Animationsstilen.

Behalten Sie eine <Sequence> bei, wenn sie gemeinsames Timing für mehrere Geschwister bereitstellt, eine Komponente umschließt, die kein Timing verarbeitet, oder Abmessungen bereitstellt, die das Kind nicht direkt akzeptieren kann. Verwaltete Roots akzeptieren auch width und height für useVideoConfig(). Behalten Sie <TransitionSeries.Sequence> für aufeinanderfolgendes Layout, mit oder ohne Übergänge; direkte from-Eigenschaften ersetzen diese Verhaltensweisen nicht.

Jedes unabhängig bearbeitbare Element erhält einen eigenen JSX-Knoten

Der Studio bearbeitet den JSX-Quellknoten, der ein Element erstellt hat. Wenn mehrere Laufzeitelemente vom selben JSX-Knoten stammen, teilen sie sich ein gemeinsames Quellbearbeitungsziel.

Schreiben Sie für jede Kompositionsregistrierung, jeden Clip, jede Szene, jede Ebene oder jede Sequenz, die einzeln bearbeitbar sein soll, einen separaten JSX-Knoten und behalten Sie deren bearbeitbare Eigenschaften auf diesem Knoten. Dies gilt für <Composition>, <Still>, integrierte Medienkomponenten, <Sequence>, <TransitionSeries.Sequence> und benutzerdefinierte Komponenten.

Beispiel für eine bearbeitbare Timeline:

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

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

Ein .map() oder eine andere programmatische Schleife würde mehrere Laufzeitelemente aus einem JSX-Quellknoten erstellen. Dies ist für wiederholte Ausgaben geeignet, die absichtlich als eine Vorlage gesteuert werden, wie z. B. Visualisierungsbalken oder Partikel. Es ist nicht geeignet, wenn die Instanzen unabhängige IDs oder Namen, Eigenschaften, Metadaten, Timing, Reihenfolge, Löschung oder Duplizierung im Studio benötigen.

Ein HTML-Element mit Interactive interaktiv machen

Jedes HTML- und SVG-Element (außer <Img>, es ist bereits interaktiv) wie <div> kann mit Interactive interaktiv gemacht werden:

<Interactive.Div
  name="Grußkarte"
  style={{fontSize: 80, padding: 24}}
>
  Hallo
</Interactive.Div>

Dies ermöglicht das Setzen von Stilen und Keyframes im Studio. Seien Sie vernünftig; wenn eine Komponente viele Elemente hat, könnte die Timeline unübersichtlich werden.

Inline-Text bevorzugen

Wenn Text fest ist und nur einmal verwendet wird, schreiben Sie ihn direkt in das interaktive Element, anstatt ihn in eine Konstante zu extrahieren.

// 👍 Fester Text bleibt bearbeitbar
<Interactive.Div name="Titel">
  Remotion Best Practices
</Interactive.Div>

Verwenden Sie eine Eigenschaft oder Variable nur, wenn der Text dynamisch oder wiederverwendet wird.

Interaktive Elemente mit einem beschreibenden Namen versehen

Fügen Sie eine name-Eigenschaft zu Elementen hinzu, um sie leicht identifizierbar zu machen. Vermeiden Sie berechnete Namen, codieren Sie sie fest.

<>
  <Interactive.Div name="Hero-Titel" style={{fontSize: 80}}>
    Launch Day
  </Interactive.Div>
  <Img name="Avatar" src="https://remotion.media/image.jpeg" />
  <Video name="Hintergrund" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="Titel">
    Launch Day
  </Interactive.Div>
</>

Alle CSS-Stile inline halten

Der beste Weg ist, einfach ein einfaches Objekt an style zu übergeben – ohne Verweise auf Konstanten, Object Spreading oder Mathematik.

<Interactive.Div
  style={{
    fontSize: 80,
    color: 'red',
  }}
>
  Hallo Welt!
</Interactive.Div>
const baseStyle = useMemo(() => {
  return {
    fontSize: 12 // ❌ Nicht-Inline-Stile werden nicht unterstützt
  }
}, []);

<Interactive.Div
  style={{
    ...baseStyle, // ❌ Spreading wird nicht unterstützt
    color: RED, // ❌ Verweise auf Konstanten werden nicht unterstützt
    scale: frame * 10 // ❌ Mathematik wird nicht unterstützt
  }}
>
  Hallo Welt!
</Interactive.Div>

Mit interpolate() animieren

Schreiben Sie Animationen als Inline-interpolate()-Aufrufe auf der Eigenschaft, die sich ändert. Der Ausgabebereich, das Easing, die Extrapolation und die output-Eigenschaft sollten fest codierte Werte verwenden.

Der Eingabebereich kann zusätzlich durationInFrames, fps, width und height verwenden, die direkt aus useVideoConfig() destructuring extrahiert werden. Bloße Bezeichner wie durationInFrames, Multiplikation mit einer Zahl wie 2 * fps oder fps * 2 und Subtraktion einer Zahl wie durationInFrames - 1 werden unterstützt.

const {fps, durationInFrames} = useVideoConfig();

// 👍 Inline-Werte können standardisiert und mit Keyframes versehen werden
<Interactive.Div
  name="Produktkarte"
  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({d

…

[abgeschnitten für Übersetzung]

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Verwandte Skills