Communitygithub.com

Guilhermeslvg/video-portfolio

Structurer le balisage Remotion pour le rendre interactif

Qu'est-ce que video-portfolio ?

Ce skill définit les meilleures pratiques pour structurer le balisage Remotion afin de maximiser l'interactivité dans Remotion Studio. Il explique comment organiser les composants, utiliser `Interactive.withSchema` pour créer des propriétés modifiables via l'interface, et préférer des composants avec leur propre timeline pour une édition plus fluide. Le skill couvre également la gestion du timing directement sur les composants, l'utilisation de noms descriptifs pour les éléments interactifs, l'écriture de styles en ligne (au lieu de constantes ou d'objets étalés), et l'utilisation de `interpolate()` pour des animations inline. Il aborde aussi l'enregistrement de compositions connectées, le choix judicieux des champs de schéma, et l'importance de créer un nœud JSX distinct pour chaque élément éditable de manière indépendante. Ce skill est conçu pour les utilisateurs de Remotion Studio (tous les frameworks Frontend supportés par Remotion), et son but est de rendre le code source directement éditable visuellement, sans perdre en flexibilité pour le développeur.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Guilhermeslvg/video-portfolio/tree/HEAD/.agents/skills/remotion-interactivity

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Aperçu

Depuis le README du skill

video-portfolio preview

Documentation

En écrivant le balisage Remotion d'une manière spécifique, le Remotion Studio est capable de reconnaître la structure du code et le rend interactif :

  • Permettre de sélectionner des éléments en cliquant dessus
  • Permettre le glisser-déposer, le redimensionnement et la rotation
  • Éditer les styles CSS
  • Rendre les images clés et les valeurs d'assouplissement modifiables

Si le balisage est trop complexe pour que Studio le rende interactif, les valeurs deviennent grisées.

Préférer les composants interactifs avec leurs propres timelines

Utilisez Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'}) pour les composants qui remplissent leur parent. Le wrapper fournit leur racine visuelle et l'élément de timeline, et masque le débordement par défaut, de sorte que le composant interne peut renvoyer un fragment. Placez le timing, le préchargement, les dimensions et les styles par instance sur le composant exporté.

Gardez les visuels qui vont ensemble dans le même composant, afin qu'ils se déplacent et se découpent ensemble. Un conteneur de mise en page interne peut toujours fournir l'alignement, le découpage dans une zone plus petite ou un groupe animé ; un conteneur qui ne fait que remplir la scène est généralement inutile. Les cartes, les boîtes de texte et les SVG avec leurs propres limites peuvent conserver la valeur par défaut layout: 'none' et transmettre style à leur racine visuelle.

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',
});

Choisir les champs du schéma

Chaque clé du schéma est une prop que Studio peut lire et éditer au site d'appel. Les props qui ne sont pas dans le schéma fonctionnent toujours, mais ne sont pas modifiables dans Studio. Les clés peuvent utiliser la notation par points, comme style.color.

TypeUtilisation pourImages clés possibles
stringChaînes de caractères, comme un titre, une instruction ou un extrait de codeNon
numberNombres, avec min, max, step, integer optionnelsOui
booleanInterrupteurs On/OffOui (hold)
colorChaînes de couleur CSSOui
enumUn choix entre variants, chacun avec son propre schéma imbriquéOpt-in (hold)
arrayListes de nombres, couleurs, énumérations et plusNon
assetSources média, avec assetType optionnelNon
font-familyFamille de police CSSNon
font-weightGraisse de policeOui
translate, scale, rotation-css, rotation-degrees, transform-originTransformationsOui
uv-coordinateUn point [x, y] normalisé sur l'élémentOui
svg-pathDonnées de chemin SVGOui
remotion-captionsDonnées Caption[]Non
hiddenUne valeur conservée en dehors des contrôles—

Chaque champ a besoin d'une valeur default et doit avoir une description, que Studio affiche comme étiquette. Définissez keyframable: false pour afficher un contrôle statique.

Exposez le contenu et l'apparence par instance, comme le texte et une couleur d'accentuation. N'ajoutez pas de champs pour style.translate, style.scale, style.rotate, style.transformOrigin et style.opacity : wrapInSequence: true les ajoute déjà.

Pour rendre les propriétés CSS de la racine modifiables par instance, étalez les fragments de schéma intégrés :

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

Également disponibles : Interactive.borderSchema, et Interactive.captionsSchema pour les composants qui acceptent les sous-titres. Pour les composants qui acceptent des enfants React.ReactNode, ajoutez ...Interactive.childrenSchema. Il fournit des contrôles pour le contenu des enfants et peut évoluer pour prendre en charge un contenu plus riche. Actuellement, il utilise un champ de chaîne intitulé "Text" avec une valeur par défaut de ''. Si un composant nécessite des enfants de type chaîne uniquement, déclarez un champ children explicite avec type: 'string' à la place. Interactive.textSchema contrôle les styles typographiques ; childrenSchema contrôle le contenu. Le fragment enfants n'est pas inclus dans baseSchema ni automatiquement ajouté aux composants personnalisés. Le balisage imbriqué et les enfants calculés sont actuellement en lecture seule. text-content est un alias obsolète pour le type de champ string. Voir InteractivitySchema pour toutes les options.

Enregistrer les composants réutilisables en tant que compositions connectées

Enregistrez les scènes importantes et les composants réutilisables avec leurs propres couches ou animation en tant que compositions autonomes. Studio appelle ces compositions connectées : elles peuvent être ouvertes dans leur propre timeline tout en partageant la même implémentation de composant avec la vidéo parent.

Définissez les styles requis et chargez les polices à l'intérieur du composant pour qu'il fonctionne sans son parent. Voir l'indépendance parent.

Enregistrez la même référence de composant exporté que le parent rend. Pour l'exemple ci-dessus, utilisez component={Scene}, pas SceneInner ou un wrapper en ligne. Avec wrapInSequence: true, aucun <Sequence> supplémentaire n'est nécessaire pour la connexion.

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',
    }}
  />
);

Choisissez un ID unique, des valeurs par défaut en ligne représentatives, et des dimensions, fps, et durée adaptés à l'aperçu du composant. Les valeurs par défaut d'enregistrement ne copient pas automatiquement les props d'une instance parent. Voir compositions connectées pour les informations d'enregistrement et d'extraction.

Placer le timing directement sur le composant

Évitez un <Sequence> séparé autour d'un composant qui accepte déjà des props de timing. Les composants interactifs intégrés et les composants personnalisés créés avec wrapInSequence: true peuvent recevoir name, from, durationInFrames, trimBefore, playbackRate, et premountFor directement.

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

Lors de la suppression d'une séquence redondante, déplacez son timing et son nom vers l'enfant. Préservez tout timing enfant existant ; ne remplacez pas et n'appliquez pas deux fois les décalages. Un appel à useCurrentFrame() à l'intérieur du composant lit son image locale, tandis qu'une expression de style au site d'appel utilise toujours l'image de l'appelant. Préservez cette distinction lors du déplacement des styles d'animation.

Gardez un <Sequence> lorsqu'il fournit un timing partagé pour plusieurs frères, enveloppe un composant qui ne gère pas le timing, ou fournit des dimensions que l'enfant ne peut pas accepter directement. Les racines gérées acceptent également width et height pour useVideoConfig(). Gardez <TransitionSeries.Sequence> pour une mise en page consécutive, avec ou sans transitions ; les props from directes ne remplacent pas ces comportements.

Donner à chaque élément modifiable indépendant son propre nœud JSX

Le Studio édite le nœud source JSX qui a créé un élément. Si plusieurs éléments au moment de l'exécution proviennent du même nœud JSX, ils partagent une seule cible d'édition de source.

Pour chaque enregistrement de composition, clip, scène, couche ou séquence qui devrait être modifiable indépendamment, écrivez un nœud JSX séparé et conservez ses props modifiables sur ce nœud. Cela s'applique à <Composition>, <Still>, les composants média intégrés, <Sequence>, <TransitionSeries.Sequence> et les composants personnalisés.

Par exemple, créez une timeline modifiable comme ceci :

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>

Un .map() ou une autre boucle programmatique créerait plusieurs éléments au moment de l'exécution à partir d'un seul nœud source JSX. Cela est approprié pour une sortie répétée qui est intentionnellement contrôlée comme un modèle unique, comme des barres de visualisation ou des particules. Ce n'est pas approprié lorsque les instances ont besoin d'ID ou de noms, de props, de métadonnées, de timing, d'ordre, de suppression ou de duplication indépendants dans Studio.

Rendre un élément HTML interactif avec Interactive

Tout élément HTML et SVG (sauf <Img>, qui est déjà interactif) comme <div> peut être rendu interactif à l'aide de Interactive :

<Interactive.Div
  name="Carte de vœux"
  style={{fontSize: 80, padding: 24}}
>
  Bonjour
</Interactive.Div>

Cela permet de définir des styles et des images clés dans Studio. Soyez raisonnable : si un composant a de nombreux éléments, la timeline pourrait devenir désordonnée.

Préférer le texte en ligne

Si le texte est fixe et utilisé une seule fois, écrivez-le directement à l'intérieur de l'élément interactif au lieu de l'extraire dans une constante.

// 👍 Le texte fixe reste modifiable
<Interactive.Div name="Titre">
  Meilleures pratiques Remotion
</Interactive.Div>

Utilisez une prop ou une variable uniquement lorsque le texte est dynamique ou réutilisé.

Donner aux éléments interactifs un nom descriptif

Ajoutez une prop name aux éléments pour les rendre facilement identifiables. Évitez les noms calculés, codez-les en dur.

<>
  <Interactive.Div name="Titre principal" style={{fontSize: 80}}>
    Jour de lancement
  </Interactive.Div>
  <Img name="Avatar" src="https://remotion.media/image.jpeg" />
  <Video name="Arrière-plan" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="Titre">
    Jour de lancement
  </Interactive.Div>
</>

Garder tous les styles CSS en ligne

La meilleure façon est de simplement passer un objet simple à style - sans références à des constantes, sans étalement d'objets, sans calculs.

<Interactive.Div
  style={{
    fontSize: 80,
    color: 'red',
  }}
>
  Bonjour tout le monde !
</Interactive.Div>
const baseStyle = useMemo(() => {
  return {
    fontSize: 12 // ❌ Les styles non en ligne ne sont pas pris en charge
  }
}, []);

<Interactive.Div
  style={{
    ...baseStyle, // ❌ L'étalement n'est pas pris en charge
    color: RED, // ❌ Les références à des constantes ne sont pas prises en charge
    scale: frame * 10 // ❌ Les calculs ne sont pas pris en charge
  }}
>
  Bonjour tout le monde !
</Interactive.Div>

Animer avec interpolate()

Écrivez les animations sous forme d'appels interpolate() en ligne sur la propriété qui change. La plage de sortie, l'assouplissement, l'extrapolation et la propriété output doivent utiliser des valeurs codées en dur.

La plage d'entrée peut également utiliser durationInFrames, fps, width et height déstructurés directement à partir de useVideoConfig(). Les identifiants simples tels que durationInFrames, la multiplication par un nombre comme 2 * fps ou fps * 2, et la soustraction d'un nombre comme durationInFrames - 1 sont pris en charge.

const {fps, durationInFrames} = useVideoConfig();

// 👍 Les valeurs en ligne peuvent être standardisées et animées par images clés
<Interactive.Div
  name="Carte produit"
  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'
      }
    )
  }}
>
  Bonjour tout le monde !
</Interactive.Div>
// ❌ Les fonctions d'assistance en dehors du style ne sont pas prises en charge pour l'interpolation
const springEasing = Easing.spring({damping: 200}); // Ne peut pas être référencée

<Interactive.Div
  name="Carte produit"
  style={{
    scale: interpolate(frame, [0, fps], [0, 1], {
      easing: springEasing, // ❌ Easing non en ligne
      output: 'perceptual-scale',
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp'
    })
  }}
>
  Bonjour tout le monde !
</Interactive.Div>

Écrivez les valeurs de sortie directement dans l'appel interpolate() ; ne construisez pas de plage de sortie via des variables, des calculs ou des appels map(). Les propriétés de transformation simples (translate, scale, rotate) sont préférées aux transformations combinées comme transform.

La propriété style sur un élément interactif est spéciale : elle reçoit toutes les ressources interactives de Studio. Ne passez jamais style à des éléments non interactifs internes ; au lieu de cela, passez une prop dédiée que le composant applique manuellement. Cela permet à Studio de modifier la propriété spécifique plutôt que de l'écraser. Voir la séparation des styles.

Types de sortie pris en charge

Le paramètre output contrôle comment le résultat interpolé est transmis à la propriété :

  • 'perceptual-scale' (par défaut pour l'échelle) – normalise la perception de l'échelle
  • 'degrees' (par défaut pour la rotation) – rotation en degrés
  • 'translate' – valeur de traduction x y
  • 'number' – valeur numérique directe (pour l'opacité, etc.)

Le type de sortie est facultatif et peut être omis ; il est déterminé par la propriété. Lorsqu'il est défini, il doit être une chaîne de caractères en ligne. Voir le type d'output de interpolation pour plus de détails.

Individual skills in this repo

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

Skills associés