HyperFrames Core
Le framework de sortie est Remotion. Une composition est un arbre de composants React enregistré avec <Composition> ; la trame courante est la seule horloge ; la lecture des médias appartient aux composants <Video> / <Audio> de Remotion, pas au DOM.
Cette compétence est le contrat technique : comment construire un projet. Le corps ci-dessous est le guide de construction ; les détails par sujet se trouvent dans references/, à lire à la demande. Les autres préoccupations vivent dans les compétences de domaine sœurs : hyperframes-animation, hyperframes-creative, media-use, hyperframes-cli, hyperframes-registry. La carte des capacités dans /hyperframes indique ce que chacune couvre.
Note de portage. Ce contrat a remplacé un précédent contrat basé sur HTML plus
data-*plus GSAP-timeline en pause. Si vous trouvez un projet dont le fichierindex.htmlportedata-composition-id/data-start/data-duration, ou un fichier de référence décrivantwindow.__timelines, vous avez affaire à l'ancien contrat. Lisezreferences/legacy-html-contract.mdavant d'y toucher.
Références
| Fichier | À lire pour… |
|---|---|
references/minimal-composition.md | démarrer à partir du squelette de projet rendable le plus petit |
references/project-structure.md | organiser src/, diviser les scènes en composants, décider entre monolithique ou modulaire |
references/timing.md | convertir des secondes en trames, placer une <Sequence>, utiliser <Series> / <TransitionSeries>, chronométrer une scène par rapport à une autre |
references/layers-and-order.md | empiler les couches, contrôler l'ordre z, faire chevaucher deux scènes sur le même intervalle |
references/props-and-schemas.md | déclarer les props, écrire le schéma Zod, câbler defaultProps et calculateMetadata |
references/media.md | placer <Video> / <OffthreadVideo> / <Audio> / <Img>, rogner, régler le volume, utiliser staticFile |
references/determinism-rules.md | garder le rendu accessible et reproductible ; les interdictions ; le chargement asynchrone des assets avec delayRender |
references/editing-recipes.md | les recettes de coupe / rognage / réorganisation / retiming / gel / caméra / masque / fondu / audio et leurs limites |
references/legacy-html-contract.md | reconnaître et migrer un projet encore sur l'ancien contrat HTML + data-* |
references/tailwind.md | travailler dans un projet Tailwind v4 |
references/storyboard-format.md | rédiger un plan STORYBOARD.md (+ le manifeste parsé) |
references/review-loop.md | exécuter les passes de revue plan → esquisse → construction sur un tableau live |
references/production-loop.md | passer d'un plan approuvé à une vidéo livrée |
references/brief-contract.md | les règles de base du brief : dérivation du mode, registre de champs partagé, invariants de questions |
references/brief-format.md | rédiger BRIEF.md |
references/script-format.md | rédiger le SCRIPT.md optionnel de narration verrouillée |
references/subagent-dispatch.md | mapper les verbes de dispatch de sous-agent à votre harnais |
references/frame-worker-core.md | le contrat partagé du rôle de frame-worker |
Pour les spécificités d'animation (configurations de ressort, easing, les adaptateurs runtime) allez voir hyperframes-animation.
Construire une composition
La forme d'un projet
package.json # remotion, react, react-dom ; scripts pour studio/render
remotion.config.ts # configuration de rendu (codec, format d'image, surcharges Tailwind/webpack)
tsconfig.json
public/ # tout ce que `staticFile()` résout
src/
index.ts # registerRoot(Root)
Root.tsx # chaque <Composition> vit ici
<Nom>/
index.tsx # le composant de premier niveau de la composition
scenes/*.tsx # un composant par scène
schema.ts # schéma Zod + type de props inféré
src/index.ts est le point d'entrée et fait exactement une chose :
import { registerRoot } from "remotion";
import { Root } from "./Root";
registerRoot(Root);
Enregistrer la composition
Tout élément rendable est un <Composition> dans Root.tsx. L'id est ce que le CLI rend par nom.
import { Composition } from "remotion";
import { LaunchVideo } from "./LaunchVideo";
import { launchSchema } from "./LaunchVideo/schema";
const FPS = 30;
export const Root: React.FC = () => (
<>
<Composition
id="launch"
component={LaunchVideo}
durationInFrames={45 * FPS}
fps={FPS}
width={1920}
height={1080}
schema={launchSchema}
defaultProps={{ productName: "Acme", accent: "#5B8CFF" }}
/>
</>
);
durationInFrames est la durée de rendu. C'est en trames, pas en secondes, et se tromper est l'erreur de rédaction la plus courante. Dérivez-la d'une constante FPS nommée plutôt que d'écrire 1350.
La trame est la seule horloge
Chaque valeur animée est une fonction pure de useCurrentFrame(). C'est le contrat qui rend un rendu accessible : le moteur de rendu saute à la trame 812 à froid et doit obtenir les mêmes pixels qu'il obtiendrait en lisant en continu.
import { AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate, spring } from "remotion";
export const Title: React.FC<{ text: string }> = ({ text }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const enter = spring({ frame, fps, config: { damping: 18 } });
const y = interpolate(enter, [0, 1], [40, 0]);
return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<h1 style={{ opacity: enter, transform: `translateY(${y}px)` }}>{text}</h1>
</AbsoluteFill>
);
};
Toujours serrer un interpolate qui mappe une plage de trames, sinon la valeur continue d'extrapoler au-delà de la plage et un fondu devient une opacité de 4 :
const opacity = interpolate(frame, [0, 15], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
Le timing est <Sequence>
<Sequence> décale temporellement ses enfants : à l'intérieur, useCurrentFrame() renvoie 0 au début propre de la séquence. Ce rebasage local est tout l'intérêt, car un composant de scène n'a jamais besoin de savoir où il se situe sur la timeline maîtresse.
<AbsoluteFill>
<Sequence durationInFrames={90}>
<Hook />
</Sequence>
<Sequence from={90} durationInFrames={150}>
<Feature />
</Sequence>
<Sequence from={240} durationInFrames={120}>
<Close />
</Sequence>
</AbsoluteFill>
Pour une série de scènes consécutives, <Series> calcule les décalages afin qu'une modification de durée ne cascade pas en correction manuelle de chaque from ultérieur :
<Series>
<Series.Sequence durationInFrames={90}><Hook /></Series.Sequence>
<Series.Sequence durationInFrames={150}><Feature /></Series.Sequence>
<Series.Sequence durationInFrames={120}><Close /></Series.Sequence>
</Series>
<Series.Sequence offset={-15}> tire une scène plus tôt pour chevaucher sa prédécesseure, ce qui permet d'obtenir un span de fondu sans arithmétique. Tous les détails, plus <TransitionSeries> pour les transitions basées sur la présentation, dans references/timing.md.
Les couches remplacent les pistes
Il n'y a pas d'index de piste. L'empilement est l'ordre DOM à l'intérieur d'un <AbsoluteFill> : les frères ultérieurs s'affichent par-dessus. Une piste de voix-off, un fond musical et trois scènes visuelles sont cinq frères.
<AbsoluteFill>
<Audio src={staticFile("music.mp3")} volume={0.18} />
<Audio src={staticFile("vo.mp3")} />
<Sequence durationInFrames={90}><Hook /></Sequence>
<Sequence from={90}><Feature /></Sequence>
<Overlay /> {/* s'affiche par-dessus tout ce qui précède */}
</AbsoluteFill>
<Sequence layout="none"> supprime le wrapper implicite <AbsoluteFill>. Utilisez-le lorsque l'enfant doit participer à la disposition flex ou grid du parent au lieu d'être positionné en absolu.
Les médias appartiennent à Remotion
N'utilisez jamais une balise <video>, <audio> ou <img> brute. Les composants de Remotion sont ce qui permet au moteur de rendu de chercher et d'extraire les trames de manière déterministe.
| Besoin | Utilisez |
|---|---|
| Vidéo dans le rendu | <OffthreadVideo src> (préférez-le) |
| Vidéo nécessitant un contrôle au niveau DOM | <Video src> |
| Audio | <Audio src volume trimBefore trimAfter> |
| Image fixe | <Img src> |
| Page intégrée | <IFrame src> |
Tout fichier sous public/ | staticFile("dossier/nom.ext") |
<OffthreadVideo> extrait les trames avec FFmpeg en dehors du navigateur, ce qui est à la fois plus rapide et plus précis pour le rendu ; recourez à <Video> uniquement lorsque vous avez besoin de l'élément réel (par exemple pour piloter quelque chose avec currentTime). staticFile() est obligatoire pour les assets locaux. Une chaîne nue "/music.mp3" fonctionne dans Studio puis échoue dans un rendu Lambda. Le rognage, les rampes de volume et la vitesse de lecture sont dans references/media.md.
Props et schéma
Les entrées de composition sont des props React, typées par un schéma Zod afin que Studio affiche des contrôles modifiables et qu'une charge utile --props incorrecte échoue bruyamment au lieu de rendre une vidéo erronée.
import { z } from "zod";
import { zColor } from "@remotion/zod-types";
export const launchSchema = z.object({
productName: z.string(),
accent: zColor(),
features: z.array(z.object({ title: z.string(), body: z.string() })),
});
export type LaunchProps = z.infer<typeof launchSchema>;
Lorsque la durée dépend des props (une liste de fonctionnalités de longueur inconnue, un fichier audio de longueur inconnue), calculez-la dans calculateMetadata plutôt que de coder en dur une estimation. Voir references/props-and-schemas.md.
Règles non-négociables
Des bugs silencieux qu'une vérification de type ne détectera pas. Justification complète dans references/determinism-rules.md.
- Pas d'horloge murale. Pas de
Date.now(), pas denew Date(), pas deperformance.now(). Le temps vient deuseCurrentFrame(). - Pas d'aléatoire non ensemencé.
Math.random()donne une valeur différente par trame, ce qui stroboscope. Utilisezrandom(seed)de Remotion : même graine, même valeur, à chaque trame et chaque rendu. - Pas d'animation pilotée par l'état.
useState/useReducer/useEffectne doivent rien piloter de visuel. Le moteur de rendu monte les trames dans le désordre et dans des workers parallèles ; l'état accumulé en jouant en avant n'existe pas. Dérivez deframeà la place. - Pas d'animations CSS, de transitions ou de
requestAnimationFrame. Elles s'exécutent sur l'horloge propre du navigateur, que le moteur de rendu n'avance pas. Chacune d'elles rend une première trame figée. - Attendez le travail asynchrone avec
delayRender. Un fetch, une police, une disposition mesurée : appelezdelayRender()avant etcontinueRender(handle)après, sinon la trame capture avant que le travail ne soit terminé. - Serrer vos interpolations. Un
interpolatenon serré extrapole en dehors de la plage d'entrée. - Positionnement absolu pour tout ce qui est animé. Un élément transformé à l'intérieur du flux normal entraîne ses frères dans son mouvement.
Modifier des compositions existantes
- Lisez les fichiers d'abord. Préservez le timing, les props, les identifiants et les chemins d'assets non liés.
- Respectez l'
idde la<Composition>existante. Le CLI, tout script de rendu et l'IC adressent les compositions par cet identifiant. En renommer un est un changement cassant. - Ajouter une scène : préférez
<Series>pour que les décalages se recalculent. Dans une liste<Sequence>avec décalages manuels, corrigez chaquefromultérieur. - Changer une durée : vérifiez
durationInFramessur la<Composition>également. Une scène qui dépasse la durée de la composition est silencieusement tronquée. - Ajouter un composant : gardez-le comme une fonction pure de props et de
frame.
Validation
Les détails de la commande sont dans hyperframes-cli.
-
npx tsc --noEmit, pour que le schéma et les types de props correspondent effectivement -
npx remotion still <id> out/probe.png --frame <n>à quelques milieux de scène, et examinez chacun -
npx remotion studiopour la revue, afin que l'utilisateur puisse scruter et ajuster les props lui-même -
npx remotion render <id>seulement après l'approbation de l'utilisateur