Communitygithub.com

RedCarpG/animation-capture

Capture des éléments animés image par image à intervalles fixes pour créer des GIF comparatifs avant/après ou des feuilles de contact, destinés à la validation, à la comparaison de modifications ou à l'insertion dans des documentations et PR.

Qu'est-ce que animation-capture ?

Ce skill permet de capturer de manière déterministe des animations provenant de pages web (CSS transitions/animations, element.animate(), requestAnimationFrame), de canvas, de jeux, de scènes three.js/VRM, de PixiJS ou de Live2D. Il utilise un shim d'horloge fictive pour garantir que chaque image est capturée au même temps logique, indépendamment de la vitesse de rendu du navigateur. Le workflow comprend : l'enregistrement des images avec record.mjs (Chrome headless + horloge fictive), la création de GIF comparatifs avec make_gif.py (variantes côte à côte, palette partagée) et l'utilisation optionnelle de clock-shim.js pour un contrôle temporel précis. Il gère automatiquement les variantes (par ex. avant/après, activation/désactivation), le cadrage, et évite les pièges connus (horloge virtuelle Chrome peu fiable, rafraîchissement Vite, chargement Live2D, etc.). Il est recommandé pour toute demande de capture d'animation, de création de GIF ou de comparaison de mouvements, même si le terme 'GIF' n'est pas explicitement mentionné.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/RedCarpG/redcarpg-skills/tree/HEAD/skills/animation-capture

Demander à votre IA préférée

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

Documentation

Capturer une animation en GIF comparatif

Les défauts et améliorations d'une animation ne se voient pas sur une simple capture d'écran, et il est difficile de les juger à l'oral. Il faut la filmer : une même animation, avant et après modification (ou activée/désactivée), capturée image par image sur la même échelle de temps, juxtaposée en GIF, pour que l'utilisateur puisse trancher d'un coup d'œil.

Le principe central est le suivant : c'est nous qui avançons le temps, pas l'horloge réelle. Le navigateur headless peut être lent à dessiner (surtout en rendu logiciel), n'affichant que quelques images par seconde. Avec une horloge réelle, chaque capture s'arrête à un moment différent, et les deux variantes ne correspondent jamais. On fait donc en sorte que le temps dans la page n'avance que lorsqu'on lui dit « avance de 40 ms », puis on capture une image, on avance à nouveau, on recapture. Ainsi, quelle que soit la lenteur de la machine, la n-ième image correspond toujours à n × 40 ms, les deux variantes correspondent image par image, et deux captures d'une même variante sont identiques (testé : 75 images identiques octet par octet).

1. Réfléchir d'abord : le GIF pour l'humain, les chiffres pour la vérification

Le GIF est adapté à la validation, à la comparaison et à l'insertion dans de la documentation, mais pas pour trouver les défauts. Exemple réel : une main qui se lève lentement, le genou reste plié à 18° pendant une seconde – cela a été détecté en lisant les angles image par image, pas dans le GIF. Donc :

  1. D'abord, enregistrer les valeurs clés image par image (angles, positions, hauteurs, transparence, glissement du pied), une fois pour chaque variante (activée/désactivée), et vérifier si les chiffres sont corrects ;
  2. Ensuite, capturer le GIF pour validation humaine.

La méthode pour enregistrer les valeurs est décrite dans references/hook.md (écrire une fonction de capture dans la page, qui renvoie les valeurs de chaque image en même temps que la capture visuelle).

2. Choisir l'horloge

HorlogeQuand l'utiliserFaut-il modifier la page ?
shim (par défaut)Presque toujours : transitions CSS, animations CSS, element.animate(), animations JS avec rAF, canvas, three.js, PixiJS, Live2DNon. clock-shim.js est injecté avant les scripts de la page, remplace performance.now, Date.now, requestAnimationFrame, setTimeout/setInterval, et Math.random avec une graine fixe ; les animations CSS/WAAPI sont figées et alignées sur le même instant
hookPour capturer des valeurs depuis un modèle (frame par frame), ou si l'application a déjà une boucle pas à pas et qu'on veut éviter la capture d'écran du navigateurÉcrire window.__capture(...) dans la page, voir references/hook.md

Ne pas utiliser l'horloge virtuelle Chrome intégrée (Emulation.setVirtualTimePolicy) : testé, les timestamps rAF et performance.now ne correspondent pas (compteurs négatifs), et les animations ne suivent pas le pas donné.

Ce que l'horloge fictive ne contrôle pas : les animations dans les Web Workers, OffscreenCanvas dessiné dans un worker, <video>, new Date() (seulement Date.now est remplacé), crypto.getRandomValues. Si vous rencontrez ces cas, passez à hook ou remplacez-les dans la page de test.

3. Étapes

  1. Préparer la page et les variantes. Utilisez de préférence des paramètres d'URL pour les variantes (?v=off, ?v=on), la page les lit et bascule ; ou utilisez --setup pour exécuter du JS de basculement avant la capture. Placez les éléments locaux (modèles, pages de test) en dehors du dépôt git, supprimez-les après utilisation.
  2. Lancer la page. Utilisez file:///… pour les fichiers locaux ; pour les projets nécessitant un build (Vite, etc.), lancez un serveur de développement sur un port libre (ne touchez pas aux fenêtres des autres sessions sur la même machine). Vite optimise les dépendances au premier chargement d'une nouvelle page, puis la recharge automatiquement – si la capture se fait en plein milieu, une erreur « window.xxx is undefined » peut survenir : ouvrez d'abord la page dans un navigateur pour la préchauffer, ou relancez la capture après l'erreur.
  3. Capturer.
    node <skill>/scripts/record.mjs --out frames --fps 25 --seconds 3 --clip "#stage" \
      --ready "window.model != null" --setup "window.model.motion('TapBody', 0)" \
      --variant "off=http://127.0.0.1:5173/lab.html?v=off" --variant "on=http://127.0.0.1:5173/lab.html"
    
    • --clip : ne capture que la zone définie (sélecteur CSS) ; sans cela, capture toute la fenêtre (taille définie par --width, --height).
    • --ready : expression indiquant que le chargement (modèle, textures asynchrones) est terminé. Pendant l'attente, l'horloge fictive ne tourne pas : si elle tournait, le moment de début et les nombres aléatoires utilisés différeraient entre deux captures (testé). Si la page utilise setTimeout pour son chargement, ajoutez --ready-advance 1.
    • --wait : temps fictif (en ms) à faire avancer après le chargement pour stabiliser les animations d'entrée, les ressorts, etc. ; --setup s'exécute après, puis la capture commence immédiatement. --setup peut contenir des await, la valeur de retour est ignorée (les objets three.js sont trop grands pour être renvoyés via CDP).
    • Le script lance lui-même Chrome headless (répertoire de configuration dédié), et le ferme après la capture. Ne pas laisser de processus résiduels.
    • Par défaut, utilise le rendu GPU (--render gpu) ; un message « Rendu WebGL : … » s'affiche au début. Si le message mentionne SwiftShader, le GPU n'est pas utilisé, le CPU sera saturé (plusieurs cœurs) – surtout visible avec three.js/VRM. Sur les machines sans GPU, ou pour comparer avec des captures antérieures en rendu logiciel, ajoutez --render cpu.
    • Les rendus GPU et logiciel produisent des bords différents : les comparaisons image par image ne se font qu'au sein d'un même type de rendu.
  4. D'abord la feuille de contact, ensuite le GIF.
    python <skill>/scripts/make_gif.py frames --variants off,on --fps 25 --width 300 --title "Hiyori · caresse" --sheet sheet.png --out compare.gif
    
    --sheet produit une PNG avec plusieurs images (par défaut 4 images réparties uniformément, --pick 0,10,20 pour spécifier) : vous vérifiez d'abord cette feuille – l'animation se déroule correctement, les variantes sont alignées, le cadrage est bon – avant de donner le GIF à l'utilisateur. Le GIF utilise une palette commune pour toutes les images, évitant les changements de couleur intempestifs. Les légendes sont écrites dans une bande blanche au-dessus de l'image, sans chevaucher le contenu.
  5. Remettre à l'utilisateur : envoyez le GIF via un outil de partage de fichiers (quelques images représentatives, le reste dit où il se trouve dans un dossier) ; précisez ce que montrent les parties gauche et droite, quelle version est enregistrée, et ce qui n'a pas été vérifié (par ex. « rendu logiciel, bords légèrement plus épais qu'avec le GPU »).

4. Comment choisir les paramètres

  • 25 images par seconde, durée 2–4 secondes ; 300 pixels de large par vignette. Une image côte à côte fait environ 0,6–3 Mo (plus pour les personnages et les dégradés).
  • La durée de l'action dépend de l'action elle-même : avec hook, vous pouvez utiliser la durée du clip plus une marge (l'élan résiduel après l'arrêt doit aussi être capturé).
  • Cadrage basé sur la boîte englobante : pour les personnages 3D, utilisez Box3.setFromObject(scene) pour inclure la tête ; se baser uniquement sur le crâne peut couper les grands chapeaux ou les antennes (testé). Laissez aussi un peu d'espace sous les pieds.
  • Chaque variante ne doit différer que par l'élément à comparer : même modèle, même caméra, même action, même attente. Les parties aléatoires sont déjà contrôlées par la graine fixe, mais si la page appelle Math.random un nombre différent de fois selon la variante, les séquences suivantes seront décalées – gardez-le en tête.

5. Procédure pour différents types de pages

  • Page web (CSS, WAAPI, rAF) : pas de modification nécessaire. Pour capturer des interactions (ouverture de menu, survol), déclenchez-les dans --setup (ajout de classe, dispatchEvent). Testé : compteurs rAF, transitions CSS déclenchées après 50 ms, animations CSS avec keyframes, element.animate() fonctionnent correctement avec l'horloge fictive ; deux variantes avec des courbes d'assouplissement différentes sont alignées image par image.
  • PixiJS / Live2D : pas de modification nécessaire. Le ticker Pixi utilise rAF et performance.now, donc suit l'horloge fictive ; la physique aussi. Le runtime Cubism Live2D (live2dcubismcore.min.js) doit être chargé via une balise <script> normale avant les modules, car le moteur le vérifie au moment de l'import – s'il n'est pas trouvé, il lance l'erreur « Could not find Cubism runtime ». Testé : 75 images de Hiyori (physique activée/désactivée) identiques octet par octet.
  • three.js / VRM : pas de modification nécessaire. La boucle rAF suit l'horloge fictive (testé : le chargement d'un VRM desktop pet fonctionne directement, les différences avant/après sont visibles). Pour lire les angles des os image par image, utilisez hook (voir references/hook.md).
  • Jeux, autres applications canvas : idem. Si l'application utilise setTimeout pour sa boucle de jeu, cela tombe aussi sous le contrôle de l'horloge fictive.

6. Licences et confidentialité

  • Les GIF provenant de modèles ou d'assets créés par d'autres personnes sont soumis à leur licence. S'ils interdisent la redistribution (ex. certaines clauses VN3), vous pouvez les montrer mais ne les placez pas dans un dépôt public ou une publication. Si la licence autorise l'utilisation avec attribution, mentionnez l'auteur lors de la publication.
  • Placez les GIF et les images dans un dossier local (hors du dépôt git, ou dans un dossier temporaire dédié) et supprimez les pages de test après utilisation.

7. Relations avec les autres skills

  • Capture d'écran statique, multi-viewports, mode sombre : frontend-verification (son shot.mjs utilise l'horloge réelle ; pour les animations, utilisez ce skill).
  • Vidéo promotionnelle pour le public, avec musique : motion-showreel.
  • Rendre l'animation des personnages naturelle (couches de mouvement, physique, clips d'action) : live2d-vrm-animation.

8. Origine et maintenance

  • Origine : Cirra (desktop pet, RedCarpG/cirra-deskpet) – le 2026-11-10, en réalisant des personnages 3D avec couches de mouvement, hanches/jambes, cheveux balayés, pour montrer les différences avant/après au client ; la méthode est décrite dans docs/vrm-motion.md §11–§12 et docs/vrm-spring-force.md §8 (« Comment vérifier »). Les leçons sont dans le dépôt privé lessons/animation-capture.md (la partie 3D est aussi dans lessons/threejs-vrm-animation.md).
  • Source : RedCarpG/redcarpg-skills dans skills/animation-capture/. Le dossier ~/.claude/skills/ est une copie locale installée – ne pas modifier directement.
  • Comment modifier :
    • Modifiez dans redcarpg-skills, via PR ; incrémentez la version dans manifest.json, validez avec node scripts/validate.mjs, puis créez un tag (animation-capture-v<version>) et réinstallez la copie locale.
    • Après avoir modifié les scripts, effectuez une capture sur une page CSS, une page Live2D ou three.js pour vérifier que les deux variantes correspondent image par image, et qu'une même variante capturée deux fois est identique (comparaison avec ImageChops.difference de Pillow).
    • Mettez à jour les fichiers lessons si la procédure ou les écueils changent ; les cas de test sont dans evals/.
    • Ajoutez une ligne dans l'historique des modifications ci-dessous.
  • Historique des modifications :
    • 2026-11-12 : v1.0.0, publié dans redcarpg-skills. Chemins locaux modifiés, contenu inchangé.
    • 2026-11-10, Cirra : Première version. record.mjs (horloges shim et hook), clock-shim.js, make_gif.py. Testé sur pages web (transitions CSS, animations CSS, WAAPI, rAF), Live2D (Hiyori, physique activée/désactivée), three.js VRM (couche de mouvement activée/désactivée).
    • 2026-11-10, Cirra : Test a révélé que les animations CSS/WAAPI lancées au chargement de la page avancent de quelques ms en temps réel avant que l'horloge fictive ne prenne le relais, créant un décalage de 16,6 ms entre deux variantes ; les transitions déclenchées par timer attendent la fin de cette étape (50 ms devient 80 ms). Correction : les animations vues pour la première fois partent de 0, et à chaque exécution de timer, rAF ou début d'étape, on revérifie. Résultat : une transition de 600 ms déclenchée à 50 ms atteint exactement 8,0 px à 80 ms, et la 0e image des deux variantes est identique. Deux cas de test (comparaison d'assouplissement web, déformation de balle rebondissante) passent tous avec et sans le skill ; le skill économise le temps de réécriture des scripts de capture et évite les pièges déjà rencontrés.
    • 2026-11-10, Cirra : Capture GIF comparatif de balancement de cheveux (force centrifuge, traînée) pour Yumeka, Sendagaya Shino ; personnage 3D avec plateau tournant (placé dans un Group rotatif) et setInterval sous horloge fictive, sans code de capture dans la page.
    • 2026-11-10 : Conformément aux nouvelles règles de rédaction, la section « Maintenance » devient « Origine et maintenance ».
    • 2026-11-10, Cirra : L'utilisateur a constaté que Chrome consommait beaucoup de CPU lors de la capture 3D. record.mjs passe par défaut en rendu GPU (--render gpu ; --render cpu reste SwiftShader), affiche le moteur utilisé ; la suppression du répertoire de configuration à la sortie de Chrome peut échouer (EPERM), désormais ignoré silencieusement si impossible. Testé : sur RTX 3060 Ti, la charge de shader par image passe de 154 ms à 8 ms ; deux captures en rendu GPU : 10 images identiques octet par octet ; rendu GPU et logiciel diffèrent image par image (bords) – ne pas mélanger.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills associés