Communitygithub.com

Guilhermeslvg/video-portfolio

Remotion-Markup für Interaktivität strukturieren

Was ist video-portfolio?

Dieses Skill dokumentiert, wie Remotion-Markup strukturiert werden muss, damit die Remotion Studio-Oberfläche es als interaktiv erkennt und bearbeitbar macht. Es ermöglicht Funktionen wie Klickauswahl, Drag & Drop, Größenänderung, Rotation sowie direkte Bearbeitung von CSS-Stilen und Keyframes. Der Skill beschreibt bewährte Methoden: interaktive Komponenten mit eigenen Timelines, die Verwendung von `Interactive.withSchema()` für anpassbare Props, das Erstellen von `connected compositions` für wiederverwendbare Szenen und die Bevorzugung von Inline-Stilen und `interpolate()`-Aufrufen für Animationen. Er unterstützt verschiedene Plattformen wie Remotion Studio, Codex und Cursor, indem er klare Regeln für JSX-Knoten, Schema-Felder und Timing-Props vorgibt, damit Entwickler maximale Kontrolle über die visuelle Bearbeitung erhalten.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Guilhermeslvg/video-portfolio/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

video-portfolio preview

Dokumentation

Durch die Strukturierung von Remotion-Markup auf eine bestimmte Weise erkennt das Remotion Studio die Struktur des Codes und macht es interaktiv:

  • Auswahl von Elementen durch Anklicken
  • Drag & Drop, Größenänderung und Rotation
  • Bearbeiten der CSS-Stile
  • Keyframes und Easing-Werte bearbeitbar machen

Wenn das Markup zu komplex für das Studio 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 Elternelement ausfüllen. Der Wrapper stellt die visuelle Wurzel und das Timeline-Element bereit und schneidet Überläufe standardmäßig ab, sodass die innere Komponente ein Fragment zurückgeben kann. Setzen Sie Timing, Vorladen, Abmessungen und instanzspezifische Styles 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 in einem kleineren Bereich oder eine animierte Gruppe bereitstellen. Ein Container, der nur die Szene füllt, ist normalerweise unnötig. Karten, Textboxen und SVGs mit eigenen Begrenzungen können den Standard layout: 'none' behalten und leiten style an ihre visuelle Wurzel weiter.

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 ein Prop, das das Studio an der Aufrufstelle lesen und bearbeiten kann. Props, die nicht im Schema sind, funktionieren weiterhin, sind aber im Studio nicht bearbeitbar. Schlüssel können Punktnotation verwenden, z. B. style.color.

TypVerwendungKeyframable
stringZeichenketten, z. B. Titel, Prompt oder CodeausschnittNein
numberZahlen, mit optionalen min, max, step, integerJa
booleanEin/Aus-SchalterJa (halten)
colorCSS-FarbzeichenkettenJa
enumEine Auswahl zwischen variants, jede mit eigenem verschachteltem SchemaOpt-in (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 ausgeblendet wird—

Jedes Feld benötigt einen default-Wert und sollte eine description haben, die das Studio als Beschriftung anzeigt. Setzen Sie keyframable: false, um ein statisches Steuerelement anzuzeigen.

Machen Sie Inhalte und instanzspezifische Darstellung zugänglich, wie 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 Captions akzeptieren. Für Komponenten, die React.ReactNode-Kinder akzeptieren, fügen Sie ...Interactive.childrenSchema hinzu. Es bietet Steuerelemente für Kinderinhalte und kann sich weiterentwickeln, um umfangreichere Inhalte zu unterstützen. Derzeit verwendet es ein Zeichenkettenfeld mit der Bezeichnung "Text" und einem Standardwert von ''. Wenn eine Komponente nur Zeichenketten als Kinder erfordert, deklarieren Sie stattdessen ein explizites children-Feld mit type: 'string'. Interactive.textSchema steuert Typografiestile; childrenSchema steuert Inhalte. Das Kinder-Fragment ist nicht in baseSchema enthalten oder wird nicht automatisch zu benutzerdefinierten Komponenten hinzugefügt. Verschachteltes Markup und berechnete Kinder 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 umfangreiche Szenen und wiederverwendbare Komponenten mit eigenen Ebenen oder Animationen als eigenständige Kompositionen. Das Studio nennt diese verbundene Kompositionen: Sie können in ihrer eigenen Timeline geöffnet werden, während sie dieselbe Komponentenimplementierung wie das übergeordnete Video teilen.

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

Registrieren Sie denselben exportierten Komponentenverweis, den das Elternelement rendert. Verwenden Sie für das obige Beispiel component={Scene}, nicht SceneInner oder einen Inline-Wrapper. Mit wrapInSequence: true wird kein zusätzliches <Sequence> für die Verbindung benötigt.

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 eine für die Vorschau der Komponente geeignete Dauer. Registrierungsstandards kopieren nicht automatisch Props von einer übergeordneten Instanz. Siehe verbundene Kompositionen für Details zur Registrierung und Extraktion.

Timing direkt auf der Komponente platzieren

Vermeiden Sie ein separates <Sequence> um eine Komponente, die bereits Timing-Props 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 auf das untergeordnete Element. Bewahren Sie vorhandenes untergeordnetes Timing; überschreiben oder wenden Sie Offsets nicht doppelt an. Ein useCurrentFrame()-Aufruf innerhalb der Komponente liest ihren lokalen Frame, während ein Stilausdruck an der Aufrufstelle den Frame des Aufrufers verwendet. Bewahren Sie diese Unterscheidung beim Verschieben von Animationsstilen.

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

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

Das Studio bearbeitet den JSX-Quellknoten, der ein Element erstellt hat. Wenn mehrere Laufzeitelemente aus demselben JSX-Knoten stammen, teilen sie sich ein Quellbearbeitungsziel.

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

Erstellen Sie beispielsweise eine bearbeitbare Timeline wie folgt:

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

<TransitionSeries>
  <TransitionSeries.Sequence name="Einführung" 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 geeignet für wiederholte Ausgaben, die absichtlich als eine Vorlage gesteuert werden, wie z. B. Visualisierungsbalken oder Partikel. Es ist nicht geeignet, wenn die Instanzen im Studio unabhängige IDs oder Namen, Props, Metadaten, Timing, Reihenfolge, Löschung oder Duplizierung 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 Festlegen 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 ein Prop oder eine Variable nur, wenn der Text dynamisch ist oder wiederverwendet wird.

Interaktiven Elementen einen beschreibenden Namen geben

Fügen Sie Elementen ein name-Prop hinzu, um sie leicht identifizierbar zu machen. Vermeiden Sie berechnete Namen, hardcoden Sie sie.

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

Alle CSS-Stile inline halten

Der beste Weg ist, einfach ein einfaches Objekt an style zu übergeben – ohne Bezug auf Konstanten, Objekt-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, // ❌ Spread wird nicht unterstützt
    color: RED, // ❌ Verweis auf Konstanten wird 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 sich ändernden Eigenschaft. Der Ausgabebereich, das Easing, die Extrapolation und die output-Eigenschaft sollten hartcodierte Werte verwenden.

Der Eingabebereich kann zusätzlich durationInFrames, fps, width und height verwenden, die direkt aus useVideoConfig() destrukturiert wurden. Einfache 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 keyframed 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

... (gekürzt zur Übersetzung)

Individual skills in this repo

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

Verwandte Skills