En écrivant le balisage Remotion d'une manière spécifique, le Studio Remotion peut reconnaître la structure du code et le rendre 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 le 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 leur élément de timeline, et masque les débordements par défaut, de sorte que le composant interne peut retourner un fragment. Placez le timing, le pré-montage, 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 un alignement, un recadrage 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="Séquence d'arrière-plan"
src="https://remotion.media/video.mp4"
premountFor={fps}
objectFit="cover"
style={{width: '100%', height: '100%'}}
/>
<Interactive.Div
name="Titre de la scène"
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: 'Titre'},
} 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 propriété que le Studio peut lire et éditer au point d'appel.
Les props qui ne sont pas dans le schéma fonctionnent toujours, mais ne sont pas modifiables dans le Studio.
Les clés peuvent utiliser la notation par points, comme style.color.
| Type | Utilisation | Image clé |
|---|---|---|
string | Chaînes de caractères, comme un titre, une invite ou un extrait de code | Non |
number | Nombres, avec min, max, step, integer optionnels | Oui |
boolean | Interrupteurs marche/arrêt | Oui (maintien) |
color | Chaînes de couleur CSS | Oui |
enum | Un choix entre variants, chacun avec son propre schéma imbriqué | Optionnel (maintien) |
array | Listes de nombres, couleurs, énumérations, etc. | Non |
asset | Sources de média, avec assetType optionnel | Non |
font-family | Famille de police CSS | Non |
font-weight | Épaisseur de police | Oui |
translate, scale, rotation-css, rotation-degrees, transform-origin | Transformations | Oui |
uv-coordinate | Un point normalisé [x, y] sur l'élément | Oui |
svg-path | Données de chemin SVG | Oui |
remotion-captions | Données Caption[] | Non |
hidden | Une valeur gardée hors des contrôles | — |
Chaque champ nécessite une valeur default et devrait avoir une description, que le 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, étendez 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 des légendes.
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é "Texte" 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 ou 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.
Consultez 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. Le 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 parente.
Définissez les styles requis et chargez les polices à l'intérieur du composant pour qu'il fonctionne sans son parent. Voir indépendance parente.
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: 'Une nouvelle perspective',
}}
/>
);
Choisissez un ID unique, des valeurs par défaut en ligne représentatives, et des dimensions, fps, et durée adaptés à la prévisualisation du composant. Les valeurs par défaut de l'enregistrement ne copient pas automatiquement les props d'une instance parente. Voir compositions connectées pour les détails 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="Scène d'ouverture"
from={30}
durationInFrames={90}
premountFor={fps}
style={{opacity: 0.9}}
title="Une nouvelle 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 de décalages en double.
Un appel à useCurrentFrame() à l'intérieur du composant lit son image locale, tandis qu'une
expression de style au point 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,
encapsule 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 éditable indépendamment son propre nœud JSX
Le Studio édite le nœud source JSX qui a créé un élément. Si plusieurs éléments runtime proviennent du même nœud JSX, ils partagent une seule cible d'édition source.
Pour chaque enregistrement de composition, clip, scène, couche ou séquence qui devrait
être éditable 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="Démo" durationInFrames={150}>
<Demo />
</TransitionSeries.Sequence>
</TransitionSeries>
Un .map() ou une autre boucle programmatique créerait plusieurs éléments runtime
à 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 nécessitent des ID ou des noms,
des props, des métadonnées, un timing, un ordre, une suppression ou une duplication indépendants dans le
Studio.
Rendre un élément HTML interactif avec Interactive
Chaque élément HTML et SVG (sauf <Img>, il est déjà interactif) comme <div> peut être rendu interactif en utilisant 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 le Studio. Soyez raisonnable, si un composant a de nombreux éléments, la timeline peut devenir désordonnée.
Préférer le texte en ligne
Si le texte est fixe et n'est utilisé qu'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 héros" style={{fontSize: 80}}>
Jour du 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 du 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érence à des constantes, sans décomposition d'objets, sans calculs.
<Interactive.Div
style={{
fontSize: 80,
color: 'red',
}}
>
Bonjour 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, // ❌ La décomposition n'est pas prise en charge
color: RED, // ❌ La référence à des constantes n'est pas prise en charge
scale: frame * 10 // ❌ Les calculs ne sont pas pris en charge
}}
>
Bonjour 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 depuis useVideoConfig(). Les identifiants simples tels que durationInFrames, la multiplication par un nombre tel que 2 * fps ou fps * 2, et la soustraction d'un nombre tel que 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 le monde !
</Interactive.Div>
L'éditeur en ligne peut analyser les appels interpolate() uniquement s'ils sont écrits avec la signature de fonction standard.
const myInterpolation = interpolate(frame, [0, fps], [0, 1]) // ❌ Variable
const myOutput = interpolate(frame, [0, fps], [0, 1], {output: 'perceptual-scale'}) // ❌ Variable
<Interactive.Div
style={{
scale: myInterpolation, // ❌ Non pris en charge
rotate: myOutput, // ❌ Non pris en charge
}}
>
Bonjour le monde !
</Interactive.Div>
Utiliser Thumbnail pour les miniatures
<Thumbnail
component={Scene}
compositionalWidth={1080}
compositionalHeight={1080}
frameToDisplay={0}
{...defaultProps}
/>
Utilisez Thumbnail pour les aperçus statiques dans l'application et les vignettes ; rend plus rapidement qu'un <Video> complet. Il peut être redimensionné arbitrairement. L'utiliser uniquement en lecture, pas pour le montage.
Meilleures pratiques pour la construction de composants interactifs
Utiliser InferProps et InferPropsInner
Déduisez les types propres pour vos composants interactifs à l'aide des types d'aide exportés :
import {Interactive, InferProps, InferPropsInner} from 'remotion';
const cardSchema = {
title: {type: 'string', default: '', description: 'Titre de la carte'},
accentColor: {type: 'color', default: '#fff', description: 'Couleur d\'accentuation'},
} as const satisfies InteractivitySchema;
type CardProps = InferProps<typeof cardSchema>;
// ^? { title: string, accentColor: string }
InferProps donne un type d'objet avec les types de valeurs de schéma résolus. Il est pratique pour les composants qui consomment directement le schéma. InferPropsInner fonctionne de la même manière mais suppose que le composant est enveloppé dans Interactive.withSchema, produisant un type adapté au Component enveloppé. Utilisez-le de cette façon :
const Card: React.FC<InferPropsInner<typeof cardSchema>> = ({title, accentColor}) => {
return (
<Interactive.Div name="Carte" style={{backgroundColor: accentColor}}>
{title}
</Interactive.Div>
);
};
export const InteractiveCard = Interactive.withSchema({
Component: Card,
componentName: 'InteractiveCard',
schema: cardSchema,
});
Avec InferPropsInner, le type des props correspond exactement à ce que reçoit le composant enveloppé.
Rendre les composants réutilisables sans parent
Pour qu'un composant fonctionne indépendamment de son parent :
- Inclure des valeurs par défaut raisonnables dans la déclaration du schéma. Par exemple, fournissez un
defaultpour chaque champ de schéma. - Charger les polices localement. Utilisez
loadFont()à l'intérieur du composant ou dans un point d'entrée partagé. N'espérez pas que la police soit chargée par un parent. - Définir un contenu de secours pour les ressources. Si le composant attend des images ou vidéos d'un parent, fournissez des espaces réservés ou des ressources par défaut.
- Éviter les props de mise en page externes. Si le composant doit s'étendre sur toute la largeur ou une hauteur spécifique, gérez-le en interne via
useVideoConfig()ou des props explicites, pas en supposant une mise en page parente.
const slideSchema = {
title: {type: 'string', default: 'Titre de la diapositive', description: 'Texte du titre'},
image: {type: 'asset', default: '', description: 'Image de fond'},
} as const satisfies InteractivitySchema;
export const Slide = Interactive.withSchema({
Component: SlideInner,
componentName: 'Slide',
schema: slideSchema,
wrapInSequence: true,
});
// Utilisation :
// <Slide title="Mon titre" image="https://exemple.com/image.jpg" />
Lorsqu'il est utilisé comme composition connectée, le composant fonctionnera immédiatement avec ces valeurs par défaut.
Gérer le contenu des enfants avec childrenSchema
Pour les composants qui acceptent React.ReactNode, vous pouvez utiliser childrenSchema pour permettre l'édition du contenu des enfants dans le Studio :
import {Interactive} from 'remotion';
const boxSchema = {
...Interactive.childrenSchema,
backgroundColor: {type: 'color', default: '#eee', description: 'Couleur de fond'},
} as const satisfies InteractivitySchema;
const Box = Interactive.withSchema({
Component: ({children, backgroundColor}) => (
<Interactive.Div style={{backgroundColor, padding: 20, width: 200, height: 200}}>
{children}
</Interactive.Div>
),
schema: boxSchema,
componentName: 'Box',
});
Cela permet au Studio de fournir un champ de saisie pour le contenu textuel des enfants.
Si vous utilisez childrenSchema, assurez-vous que votre composant rend les children reçus.
Utiliser le champ d'énumération pour les variantes
Le champ enum vous permet de basculer entre plusieurs variantes de contenu, chacune avec son propre schéma imbriqué :
const cardSchema = {
variant: {
type: 'enum',
default: 'simple',
variants: {
simple: {
label: 'Simple',
schema: {
title: {type: 'string', default: 'Titre', description: 'Texte du titre'},
},
},
detailed: {
label: 'Détaillé',
schema: {
title: {type: 'string', default: 'Titre', description: 'Texte du titre'},
subtitle: {type: 'string', default: 'Sous-titre', description: 'Texte du sous-titre'},
image: {type: 'asset', default: '', description: 'Image'},
},
},
},
},
} as const satisfies InteractivitySchema;
C'est utile pour les composants qui ont différents modes d'affichage ou configurations. Le Studio affiche une liste déroulante pour sélectionner la variante, et les champs de schéma correspondants apparaissent dans les contrôles. Le champ d'énumération prend également en charge les images clés si vous définissez keyframable: true sur la définition de l'énumération.
Utiliser TechDocs pour la documentation
Si votre composant restitue de la documentation technique, envisagez d'utiliser des composants TechDocs pour les longs textes formatés :
import {TechDocs} from 'composables/tech-docs';
<TechDocs
name="Documentation"
markdown={docsContent}
/>
TechDocs affiche le contenu Markdown avec la mise en forme, les tableaux et les extraits de code. Il est optimisé pour les manuels, les guides de démarrage rapide et les spécifications d'API. Il ne prend actuellement pas en charge l'édition directe dans le Studio, mais la propriété markdown peut être rendue modifiable via un champ de type chaîne dans un schéma si nécessaire.
Dépannage : pour les composants qui ne sont pas interactifs
Si le Studio affiche les valeurs en grisé :
- Vérifiez la structure du balisage : Assurez-vous que les styles sont en ligne, que les noms des éléments sont descriptifs et que chaque élément modifiable possède son propre nœud JSX.
- Éliminez les abstractions : Supprimez les constantes, les décompositions d'objets, les références à des variables et les calculs dans les valeurs de
style. - Validez les appels
interpolate(): Vérifiez qu'ils utilisent la forme standard :interpolate(frame, [input, ...], [output, ...], {options}). - Confirmez l'enregistrement de la composition : Si le composant est destiné à être une composition connectée, confirmez qu'il est enregistré via
CompositiondansRoot.tsx.
Rédigé par l'équipe Remotion. Consultez les documents officiels Remotion pour des informations plus détaillées.
