Aller au contenu principal

Timeline et étapes

Le moteur compile votre tableau timeline en une chronologie pure : à chaque instant t (ms), l'état visuel est déterminé par une fonction evaluate(timeline, t). Le seek arrière, la navigation par étapes et le SSR en découlent gratuitement.

Étapes logiques

Chaque action racine (au premier niveau de timeline) constitue une étape logique. Les boutons « Précédent » / « Suivant » du lecteur naviguent d'une étape à l'autre.

Une courte pause (STEP_GAP) sépare deux étapes consécutives : l'arrêt « Suivant » montre ainsi l'étape « posée » seule, sans chevaucher l'apparition de la suivante.

Dans l'exemple ci-dessous, les cinq actions racines forment cinq étapes : utilisez « Précédent » / « Suivant » pour avancer pas à pas.

Chargement…
timeline: [
{ type: 'comment', object: 'browser', text: '1. …' }, // étape 1
{ type: 'move', object: 'req', from: 'browser', to: 'api' }, // étape 2
{ type: 'loading', id: 'work', object: 'api' }, // étape 3
{ type: 'move', object: 'res', from: 'api', to: 'browser', wait_for: 'work' }, // étape 4
{ type: 'comment', object: 'browser', text: '2. …', keep_until_end: true }, // étape 5
],

Points d'arrêt

Les actions produisent des points d'arrêt sur lesquels la navigation peut se caler :

  • Un move produit deux points : à l'apparition de l'objet à son origine, puis à son arrivée à destination.
  • arrow, loading, set_content, comment, highlight produisent un point : leur état « posé » à la fin de l'animation.

Synchronisation entre actions

Trois mécanismes permettent de coordonner les actions :

wait_for

L'action démarre à la fin d'une autre action référencée par son id :

{ type: 'loading', id: 'dbwork', object: 'db', duration: 900 },
{
type: 'move',
object: 'rows',
from: 'db',
to: 'api',
wait_for: 'dbwork', // démarre quand le loading se termine
},

parallel

Encapsule plusieurs actions exécutées au même instant :

{
type: 'parallel',
actions: [
{ type: 'move', object: 'p1', from: 'a', to: 'b' },
{ type: 'move', object: 'p2', from: 'c', to: 'd' },
],
}

Ici, un gateway diffuse trois requêtes vers ses services : les trois paquets partent simultanément.

Chargement…

Durées personnalisées

Chaque action accepte une duration (ms). Les défauts sont :

Type d'actionDéfaut (ms)
comment, set_contentdéduit du contenu — voir ci-dessous
movedéduit de la distance — voir plus bas
arrow500
loading1200
highlight600
set_visible300
wait1000

Temps de lecture

Une action qui porte quelque chose à lire — le texte d'un comment, le panneau d'un set_content — doit rester à l'écran assez longtemps pour que ce texte soit effectivement lu. N'écrivez aucune duration et le moteur la déduit du contenu lui-même : un coût fixe pour repérer ce qui vient d'apparaître, plus la longueur divisée par une vitesse de lecture. Du code reçoit plus de temps que de la prose, un tableau plus qu'une étiquette, et le résultat est borné pour qu'une pastille de deux mots ne passe pas en un éclair et qu'un long paragraphe ne prenne pas l'animation en otage.

// Aucune duration : chaque bulle reste le temps que son propre texte demande.
{ type: 'comment', object: 'lb', text: 'Requête 1 → Backend 1' },
{ type: 'comment', text: 'Le répartiteur distribue les requêtes à tour de rôle, pour équilibrer la charge.' },

La seconde bulle ci-dessus est trois fois plus longue que la première : elle reste donc à peu près trois fois plus longtemps — sans que personne ait compté les caractères.

Le temps d'un commentaire est compté une fois la bulle entièrement apparue : elle fait d'abord son fondu (sur fade_in_ms, ou 250 ms par défaut), et le temps de lecture ne commence qu'ensuite. duration: 3000 signifie donc trois secondes de texte lisible, et non trois secondes incluant son arrivée.

Une duration explicite l'emporte toujours. C'est une intention affirmée, pas une estimation : utilisez-la dès que la longueur du contenu n'est pas ce qui doit décider du minutage — une pause dramatique, un temps tenu pour l'effet.

Si le rythme déduit vous paraît trop rapide ou trop lent pour votre public, ajustez-le en un seul endroit avec pace sur la spec, plutôt qu'action par action :

const spec = {
pace: 1.25, // un quart de temps de lecture en plus, partout où il est déduit
nodes: [...],
packets: [...],
timeline: [...],
};

pace ne met à l'échelle que les durées déduites par le moteur. Une duration que vous avez écrite vous-même reste exactement telle quelle.

astuce

Ajouter un wait juste après un comment est en général le signe que ce commentaire manquait de temps de lecture. Retirez le wait et la duration du commentaire, et laissez la longueur décider.

Temps de trajet

La même idée appliquée à l'espace : un move sans duration la déduit de la longueur de son trajet, si bien que deux sauts de longueurs différentes sont parcourus à la même vitesse apparente, plutôt que dans le même temps. L'œil lit une vitesse, pas une durée — et une vitesse qui change sans raison se lit comme une erreur.

// Aucune duration sur l'un ni sur l'autre : le trajet long prend simplement plus de temps.
{ type: 'move', object: 'rq', from: 'client', to: 'lb' },
{ type: 'move', object: 'rs', from: 'backend', to: 'client' },

Un paquet patiente aussi à son origine le temps d'être lu. Il porte du texte — un en-tête, une requête, un nombre de lignes — et le lecteur le découvre pendant qu'il est encore immobile : cette pause dure donc au moins ce que son contenu demande. Elle n'est comptée qu'une fois par paquet, le saut suivant du même trajet montrant le même texte.

Les distances sont mesurées dans un repère de référence fixe, jamais dans les pixels réels du lecteur. C'est ce qui garantit une seule chronologie par animation : redimensionnez le lecteur et la durée totale, les frontières d'étapes et la vidéo exportée restent identiques.

Ici encore, une duration explicite l'emporte. Utilisez-la quand le rythme d'un mouvement porte un sens que la distance ne peut pas exprimer — un passage de relais volontairement lent, une salve censée paraître brutale.

Persistance visuelle

Par défaut, un élément animé reste visible un court instant après la fin de son animation (ARRIVE_HOLD) puis disparaît. Trois props prolongent cette présence :

keep_until

Reste visible jusqu'au début d'une action ciblée par id :

{ type: 'arrow', id: 'A', from: 'a', to: 'b', keep_until: 'C' },
{ type: 'move', object: 'p', from: 'a', to: 'b' },
{ type: 'comment', id: 'C', object: 'a', text: 'fin' },
// La flèche A reste affichée jusqu'au début du commentaire C.

keep_until_next

Reste visible jusqu'au début de l'étape racine suivante (donc à travers la pause inter-étapes). Défauts par type d'action :

Type d'actionkeep_until_next par défaut
arrow, comment, set_content, highlighttrue
move, loading, set_visiblefalse

keep_until_end

Reste visible jusqu'à la fin de la chronologie :

{ type: 'highlight', object: 'db', keep_until_end: true },

Décalage et fondus

Trois champs affinent le timing fin d'une action, au-delà de son ordonnancement logique.

delay_ms

Décale le démarrage de delay_ms millisecondes, après la résolution de wait_for et le calage sur l'étape. Son usage principal est de séquencer des actions à l'intérieur d'un parallel (effet « cascade ») :

{
type: 'parallel',
actions: [
{ type: 'move', object: 'p1', from: 'a', to: 'b' },
{ type: 'move', object: 'p2', from: 'a', to: 'b', delay_ms: 150 },
{ type: 'move', object: 'p3', from: 'a', to: 'b', delay_ms: 300 },
],
}

Le même fan-out que ci-dessus, mais avec un delay_ms croissant : les paquets s'égrènent au lieu de partir d'un bloc.

Chargement…

Appliqué à un parallel entier, il retarde tout le groupe.

fade_in_ms / fade_out_ms

Contrôlent la durée des fondus d'apparition et de disparition (en ms). 0 donne une transition instantanée. fade_out_ms est sans effet si keep_until_end est vrai (l'élément ne disparaît jamais).

{ type: 'comment', object: 'db', text: 'Lecture', fade_in_ms: 0, fade_out_ms: 600 }

Défauts : fade_out_ms → 250 ; fade_in_ms → 250 (300 pour move).