Animer avec du Code via Motion Canvas

Quand on a besoin de créer des vidéos techniques, ajouter des animations est un bon moyen d'améliorer les explications et la compréhension. Malheureusement les logiciels d'animation classiques ne sont pas adaptés à ce type de contenu et c'est là que Motion Canvas intervient. C'est un outil open source, créé par le YouTubeur Aarthificial, qui permet de créer des animations en utilisant du code TypeScript.

Sommaire

Pourquoi animer avec du code ?

Habituellement, les animations se font de manière déclarative à l'aide de keyframes, et le logiciel interpole entre les deux positions pour créer le mouvement. C'est ce qu'on retrouve dans After Effects, Davinci Resolve et Blender.

Avec du code, on passe à une approche impérative où on décrit notre animation comme une suite de commandes (créer un cercle, attendre 2 secondes, le déplacer pendant 1 seconde). Cette approche, popularisée par Manim, apporte plusieurs avantages concrets :

Le premier, c'est la gestion du timing. Avec des keyframes, si on veut décaler un évènement dans le temps, il faut repositionner manuellement toutes les keyframes suivantes. Avec des commandes, il suffit de modifier une durée et le reste s'adapte automatiquement.

Le second avantage, c'est la possibilité d'utiliser des concepts de programmation pour contrôler l'animation. On peut utiliser des boucles pour créer de la répétition, écrire des fonctions pour réutiliser des effets communs, ou utiliser des données externes pour générer des éléments de manière procédurale. Par exemple, si on veut représenter un système solaire, il suffit de stocker les planètes dans un tableau et de boucler dessus pour créer autant de cercles que nécessaire. Faire la même chose avec After Effects serait un véritable enfer de positionnement et de gestion de durées.

Enfin, le fait d'utiliser du code plutôt qu'une interface graphique permet d'utiliser plus facilement les LLM pour assister la création d'animations. On peut prompter un modèle pour qu'il génère le code correspondant à une animation donnée (même si Motion Canvas reste un outil assez niche et que le LLM aura parfois du mal à s'adapter à sa logique).

Fonctionnement général

Installation et interface

Pour démarrer un projet Motion Canvas, on passe par npm (ou bun) :

npm init @motion-canvas@latest

On choisit un nom de projet, TypeScript ou JavaScript, et le type d'export souhaité (séquence d'images ou vidéo via FFmpeg). Ensuite, on lance le serveur de prévisualisation :

npm run start

Un serveur web démarre sur le port 9000 et offre une interface avec une timeline pour naviguer dans l'animation.

Structure d'un projet

Le point d'entrée est le fichier project.ts qui définit un projet composé de plusieurs scènes. Chaque scène est un fichier .tsx qui exporte une fonction génératrice :

import { makeScene2D } from "@motion-canvas/2d";

export default makeScene2D(function* (view) {
  view.fill("#141414");
  // ...
});

Pour définir le contenu d'une scène, on utilise une syntaxe JSX similaire à React. Les composants disponibles (Circle, Rect, Line, Txt, Code...) sont documentés dans la partie API de la documentation

import { Circle } from "@motion-canvas/2d";

export default makeScene2D(function* (view) {
  view.fill("#141414");
  view.add(<Circle size={300} fill="yellow" />);
});

Animer avec les générateurs

L'animation repose sur le mécanisme des fonctions génératrices de JavaScript. Le mot-clé yield* permet de jouer une animation et d'attendre qu'elle se termine avant de passer à la suite.

Pour animer une propriété, on passe simplement une durée en second paramètre du setter :

const circle = createRef<Circle>();

view.add(<Circle ref={circle} size={300} fill="yellow" />);

// Change la couleur en 1 seconde
yield * circle().fill("red", 1);
// Puis change la taille en 1 seconde
yield * circle().size(500, 1);

Si on veut jouer plusieurs animations en même temps, on utilise la fonction all :

yield * all(circle().fill("red", 1), circle().size(500, 1));

Pour ajouter de l'attente, on dispose de deux fonctions :

  • waitFor qui attend un nombre de secondes fixe).
  • waitUntil crée un marqueur dans la timeline que l'on peut déplacer dans l'éditeur pour synchroniser l'animation avec une voix off
yield * waitFor(2); // Attend 2 secondes
yield * waitUntil("color"); // Attend jusqu'au marqueur "color"
yield * circle().fill("green", 1);

Le waitUntil est particulièrement pratique : on peut importer l'audio dans Motion Canvas et caler visuellement les marqueurs sur les explications. Motion Canvas enregistre automatiquement les positions dans un fichier de métadonnées associé à la scène.

Les signaux

Les signaux sont des variables dynamiques dont la valeur peut évoluer dans le temps. Toutes les propriétés des éléments sont déjà des signaux, mais on peut aussi en créer manuellement pour piloter plusieurs éléments à partir d'une seule valeur :

const size = createSignal(300);

view.add(
  <>
    <Circle size={size} fill="yellow" />
    <Line
      stroke="red"
      lineWidth={4}
      startArrow
      endArrow
      points={() => [
        [0, 0],
        [size() / 2, 0],
      ]}
    />
    <Txt
      text={() => `${Math.round(size())}cm`}
      x={() => size() / 4}
      y={40}
      fontSize={50}
      fill="red"
    />
  </>,
);

yield * size(500, 1);

Ici, quand on anime le signal size, le cercle grandit, la ligne s'allonge et le texte se met à jour, le tout avec une seule instruction. C'est très utile pour représenter des relations entre éléments.

Les layouts

Par défaut, tous les éléments sont positionnés au centre de la scène (position 0, 0). Pour créer des mises en page plus complexes, Motion Canvas propose un système de layout qui fonctionne comme les Flexbox en CSS. Il suffit d'activer la propriété layout sur un conteneur :

view.add(
  <Rect layout direction="column" gap={20}>
    <Rect width={100} height={100} fill="yellow" />
    <Rect width={100} height={100} fill="green" />
  </Rect>,
);

On peut changer la direction, le gap, le padding, utiliser des pourcentages pour les dimensions, et surtout animer ces propriétés. Par exemple, animer un gap qui passe de 0 à 50 va déplacer progressivement les éléments, et redimensionner un enfant va automatiquement repositionner ses voisins.

Ce système est particulièrement pratique pour créer des animations techniques. On peut, par exemple, créer un layout avec un bloc de code à gauche (grâce au composant Code qui gère la coloration syntaxique et une prévisualisation à droite :

const previewStyle = {
  fill: "#333333",
  radius: 10,
  width: "50%",
  padding: 20,
  layout: true,
} satisfies RectProps;

view.add(
  <Rect
    layout
    gap={30}
    width={view.width()}
    height={view.height()}
    padding={20}
  >
    <Rect {...previewStyle}>
      <Code ref={code} highlighter={highlighter} code={`...`} fontSize={42} />
    </Rect>
    <Rect {...previewStyle}>{/* Prévisualisation du rendu */}</Rect>
  </Rect>,
);

Les scènes et transitions

Un projet Motion Canvas se découpe en scènes. Chaque scène est un fichier séparé qui est enregistré dans le fichier project.ts :

import example from "./scenes/example?scene";
import layout from "./scenes/layout?scene";

export default makeProject({
  scenes: [example, layout],
});

Par défaut, la transition entre deux scènes est une coupe franche. Mais on peut utiliser des transitions intégrées comme slideTransition pour créer un effet de glissement :

export default makeScene2D(function* (view) {
  yield* slideTransition(Direction.Left);
  // ...
});

Il est aussi tout à fait possible de créer ses propres transitions en animant la scène précédente et la scène suivante comme on le souhaite.

La caméra

Pour des scènes plus complexes, Motion Canvas propose un système de caméra qui permet de se déplacer et de zoomer dans la scène indépendamment des transformations appliquées aux éléments. On entoure le contenu d'un composant Camera :

const camera = createRef<Camera>();

view.add(<Camera ref={camera}>{/* Contenu de la scène */}</Camera>);

On peut ensuite piloter la caméra pour centrer sur un élément et zoomer :

yield * all(camera().centerOn(planets[0](), 1), camera().zoom(2, 1));

On peut aussi faire en sorte que la caméra suive un élément en mouvement en passant une fonction à la position :

camera().position(() => planets[3]().absolutePosition());
yield * waitFor(2);
// Puis on désengage le suivi pour se recentrer
yield * camera().centerOn(sun(), 1);

Conclusion

Motion Canvas est un outil niche qui s'adresse principalement aux personnes qui font des vidéos techniques ou des tutoriels. Son approche code-first apporte une flexibilité que les logiciels d'animation traditionnels ont du mal à offrir : réutilisabilité des animations via des composants, gestion du timing simplifiée grâce aux générateurs, et génération procédurale d'éléments via des boucles et des données.

Le temps de prise en main est un peu plus long qu'avec un logiciel graphique (il faut écrire du code avant de voir quoi que ce soit), mais au fur et à mesure que l'on se construit une bibliothèque de composants réutilisables (navigateur, bloc de code, diagrammes...), la création d'animations devient de plus en plus rapide.

Si vous faites des vidéos un peu techniques et que vous cherchez un outil pour accompagner vos explications avec des animations propres et reproductibles, je vous conseille vivement d'essayer Motion Canvas.

Publié
Technologies utilisées
Auteur :
Grafikart
Partager