Communitygithub.com

RedCarpG/animation-capture

Nimmt bewegte Inhalte in festen Zeitintervallen Bild für Bild auf und erstellt GIFs oder Bild-für-Bild-Vergleiche zur Abnahme, zum Vergleich von Änderungen oder zur Einbindung in PRs und Dokumentationen. Funktioniert mit CSS-Übergängen und -Animationen, element.animate(), requestAnimationFrame, Canvas, three.js/VRM-Charakteren und PixiJS/Live2D.

Was ist animation-capture?

Das Skill 'animation-capture' ermöglicht die deterministische Aufnahme von Animationen aus Webseiten als Einzelbilder und deren Zusammenstellung zu GIFs oder Stapelbildern (Spritesheets). Es eignet sich besonders für den Vorher-Nachher-Vergleich von Animationen, z. B. bei CSS-Übergängen, JavaScript-Animationen via requestAnimationFrame, Canvas-Spielen, three.js/VRM-Charakteren oder PixiJS/Live2D-Szenen. Der Kern des Skills besteht darin, die Zeit nicht durch die echte Systemuhr, sondern durch einen manipulierten Taktgeber (Clock Shim) zu steuern. Dadurch läuft die Animation auf jeder Maschine gleich schnell – unabhängig von der Rendering-Geschwindigkeit des Browsers. Jeder Frame wird zum exakt gleichen Zeitpunkt aufgenommen, sodass zwei Varianten derselben Animation Frame für Frame pixelgenau vergleichbar sind. Das Skill stellt drei Komponenten bereit: `record.mjs` (headless Chrome mit Fake-Clock), `make_gif.py` (erstellt GIFs mit optionalem Spritesheet) und `clock-shim.js` (injiziert den manipulierten Taktgeber). Es wird automatisch erkannt, wenn ein Benutzer eine Animation aufnehmen, ein GIF erstellen oder einen Vorher-Nachher-Vergleich anfordern möchte – auch ohne explizite Erwähnung von 'GIF'. Das Skill ist plattformunabhängig und funktioniert mit jedem modernen Browser, der headless Chrome unterstützt. Es wurde auf verschiedenen Szenarien getestet: CSS-Animationen, WAAPI, requestAnimationFrame, PixiJS, Live2D und three.js/VRM. Wichtige Fallstricke werden dokumentiert, z. B. die Unzuverlässigkeit der Chrome Virtual Time, das Problem des Seiten-Reloads bei Vite und die Notwendigkeit, den Chrome-Prozess nach der Aufnahme zu beenden.

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

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Die Animation als Vergleichs-GIF aufnehmen

Probleme und Verbesserungen bei Animationen lassen sich mit einem einzigen Screenshot nicht erkennen, und mit Worten allein kann der Benutzer keine Entscheidung treffen. Man muss sie aufnehmen: dieselbe Animation vor und nach der Änderung (oder ausgeschaltet/eingeschaltet) mit derselben Zeitleiste Bild für Bild aufnehmen, nebeneinander als GIF zeigen, und der Benutzer sieht auf einen Blick, ob es passt.

Der Kern ist nur dies: die Zeit wird von uns gesteuert, nicht von der echten Uhr. Der headless Browser malt langsam (besonders bei Software-Rendering) – vielleicht nur 10–15 Bilder pro Sekunde. Würde man die echte Zeit verwenden, stünde die Animation jedes Mal an einer anderen Stelle, und links und rechts würden nie übereinstimmen. Deshalb lassen wir die Zeit im Skript nur dann weitergehen, wenn wir sagen „jetzt 40 Millisekunden vorwärts“. Dann wird ein Bild gemacht, dann wieder vorwärts, wieder ein Bild. Egal wie langsam die Maschine ist, der n-te Frame ist immer der n-te × 40 Millisekunden. Die beiden Varianten passen Frame für Frame zusammen, und dieselbe Variante zweimal aufgenommen ergibt identische Bilder (im Test 75 Frames alle byteweise gleich).

1. Erst klar werden: GIF fürs Vorzeigen, Zahlen fürs Prüfen

GIFs sind geeignet für Abnahmen, Vergleiche und in Dokumentationen, aber nicht zum Fehlersuchen. Wir hatten den Fall: eine langsame Handhebung, dabei war das Knie 18° gebeugt für eine ganze Sekunde – das fanden wir nur durch Aufzeichnen der Winkel pro Frame heraus, im GIF war es nicht sichtbar. Also:

  1. Erst Schlüsselwerte pro Frame notieren (Winkel, Position, Höhe, Transparenz, ob der Fuß rutscht), einmal für den Zustand vor und einmal nach der Änderung, und prüfen, ob die Zahlen stimmen.
  2. Wenn die Zahlen passen, dann das GIF aufnehmen zur Entscheidung.

Die Methode zum Werte-Notieren steht in references/hook.md (eine Aufnahmefunktion im Skript, die jedes Bild gleichzeitig mit den Werten zurückgibt).

2. Taktgeber wählen

TaktgeberWann verwendenMuss die Seite geändert werden?
shim (Standard)Fast immer: CSS-Übergänge, CSS-Animationen, element.animate(), rAF-basierte JS-Animationen, Canvas, three.js, PixiJS, Live2DNein. clock-shim.js wird vor den Seiten-Skripten injiziert, übernimmt performance.now, Date.now, requestAnimationFrame, setTimeout/setInterval, Math.random bekommt einen festen Seed; CSS/WAAPI-Animationen werden pro Frame angehalten und auf denselben Moment gestellt
hookWenn man während der Aufnahme Werte aus dem Modell lesen möchte (pro Frame), oder wenn die Anwendung selbst einen Zyklus mit fester Schrittweite hat und den Browser-Screenshot umgehen möchteJa, man muss window.__capture(...) im Seiten-Skript definieren, siehe references/hook.md

Nicht verwenden: Chrome-eigene virtuelle Zeit (Emulation.setVirtualTimePolicy) – getestet, die rAF-Zeit und performance.now passten nicht zusammen (der Zähler wurde negativ), und die Animation folgte nicht der gegebenen Schrittweite.

Was die Fake-Clock nicht steuert: Animationen in Web Workern, OffscreenCanvas im Worker, <video>, new Date() (nur Date.now wurde übernommen), crypto.getRandomValues. Falls nötig, auf hook umstellen oder die entsprechenden Teile in der Testseite austauschen.

3. Schritte

  1. Seite und Varianten vorbereiten. Die Varianten am besten über URL-Parameter schalten (?v=off, ?v=on), die Seite liest den Parameter und schaltet entsprechend; oder mit --setup vor der Aufnahme JS ausführen, das umschaltet. Sachen, die nur lokal nutzbar sind (fremde Modelle, Testseiten), nicht in git legen und nach Gebrauch löschen.
  2. Seite starten. Lokale Dateien einfach mit file:///…; wenn gebündelt werden muss (Vite etc.), einen Entwicklungsserver starten, einen freien Port wählen (andere Sessions auf derselben Maschine könnten Testports belegt haben, deren Fenster nicht anfassen). Vite öffnet neue Seiten beim ersten Mal und optimiert Abhängigkeiten und lädt dann die Seite neu – wenn die Aufnahme läuft und die Seite neu geladen wird, kommt der Fehler „window.xxx ist undefined“: entweder die Seite vorher einmal im Browser öffnen, um sie vorzuwärmen, oder nach dem Fehler einfach noch einmal starten.
  3. Aufnehmen.
    node <skill>/scripts/record.mjs --out frames --fps 25 --seconds 3 --clip "#stage" \
      --ready "window.model != null" --setup "window.model.motion('TapBody', 0)" \
      --variant "Aus=http://127.0.0.1:5173/lab.html?v=off" --variant "Ein=http://127.0.0.1:5173/lab.html"
    
    • --clip schneidet nur den Bereich zu (CSS-Selektor); ohne wird das ganze Fenster aufgenommen (--width, --height bestimmen die Größe).
    • --ready: Modelle, Texturen werden asynchron geladen, schreibe einen Ausdruck, der sagt „fertig geladen“. Während des Wartens geht die Fake-Clock nicht vor: sonst wäre jedes Mal anders langsam, der Startzeitpunkt und die verwendeten Zufallszahlen wären unterschiedlich, und zwei Aufnahmen würden nicht passen (getestet). Seiten, die setTimeout zum Laden verwenden, brauchen zusätzliches --ready-advance 1.
    • --wait: Nachdem alles bereit ist, wie viele Fake-Millisekunden vergehen sollen, damit Einführungs-Animationen oder Federn zur Ruhe kommen; --setup wird danach ausgeführt, und sofort danach beginnt die Aufnahme. --setup darf await enthalten, der Rückgabewert wird verworfen (three.js-Objekte sind zu groß für CDP).
    • Das Skript startet einen eigenen headless Chrome (eigenes Konfigurationsverzeichnis) und schaltet ihn nach der Aufnahme selbst ab – ihn nicht laufen lassen.
    • Standardmäßig wird mit GPU gerendert (--render gpu), am Anfang wird „WebGL-Rendering: …“ ausgegeben. Wenn dort SwiftShader steht, wurde die GPU nicht genutzt, und die CPU wird mehrere Kerne lang voll belastet. Bei der Aufnahme von three.js/VRM würde der Benutzer sehen, dass Chrome dauerhaft CPU frisst. Auf Maschinen ohne GPU, oder wenn man Frames mit früher per Software-Rendering aufgenommenen Frames vergleichen will, --render cpu angeben.
    • GPU- und Software-Rendering erzeugen unterschiedliche Kanten; Frame-für-Frame-Vergleiche nur innerhalb derselben Rendering-Art durchführen.
  4. Zuerst das Vergleichsbild ansehen, dann das GIF ausgeben.
    python <skill>/scripts/make_gif.py frames --variants Aus,Ein --fps 25 --width 300 --title "Hiyori · Kopf tätscheln" --sheet sheet.png --out compare.gif
    
    --sheet setzt einige Bilder (Standardmäßig gleichmäßig 4 Bilder, --pick 0,10,20 gibt bestimmte vor) zu einem PNG zusammen: zuerst dieses Bild selbst ansehen, um zu bestätigen, dass die Animation läuft, links und rechts zusammenpassen und der Ausschnitt nichts abgeschnitten hat; dann das GIF an den Benutzer geben. Im GIF haben alle Frames eine gemeinsame Palette – keine Farbwechsel pro Frame. Die Beschriftung steht in einem weißen Balken über dem Bild, nicht auf dem Inhalt.
  5. Dem Benutzer übergeben: das GIF mit einem Tool schicken, das Dateien versenden kann (einmal ein paar repräsentative Aufnahmen schicken, den Rest in einem Ordner nennen); erklären, was links und rechts ist, welche Version aufgenommen wurde, und was nicht geprüft wurde (z. B. „Software-Rendering verwendet, Kanten etwas dicker als mit GPU“).

4. Parameter festlegen

  • 25 Bilder pro Sekunde, jedes 2–4 Sekunden; Einzelframe 300 Pixel breit. Zwei nebeneinander ergeben eine Datei von etwa 0,6–3 MB (bei Charakteren und Farbverläufen eher größer).
  • Die Länge des Clips richtet sich nach der Bewegung: bei hook die Clip-Länge plus ein wenig Nachlauf (die Auslaufbewegung muss auch aufgenommen werden).
  • Ausschnitt nach Bounding Box: Bei 3D-Charakteren mit Box3.setFromObject(scene) bis zum Kopf rechnen – nur den Schädel zu nehmen, kann große Helme oder Antennen abschneiden (getestet); unter den Füßen auch etwas Platz lassen.
  • Alle Varianten bis auf die zu vergleichende Stelle identisch: dasselbe Modell, dieselbe Kamera, dieselbe Bewegung, dieselbe Wartezeit. Zufällige Anteile sind durch die Fake-Clock bereits mit festem Seed versehen, aber wenn die Seite in verschiedenen Varianten mehr oder weniger Math.random-Aufrufe macht, verschiebt sich die Sequenz – das muss man bedenken.

5. Vorgehen bei verschiedenen Seitentypen

  • Webseiten (CSS, WAAPI, rAF): Seite muss nicht geändert werden. Für Interaktionen (Menü aufklappen, Hover) in --setup auslösen (class hinzufügen, dispatchEvent). Getestet: rAF-Zähler, CSS-Übergänge, die erst nach 50 ms ausgelöst werden, CSS-Keyframe-Animationen, element.animate() – funktionieren alle mit Fake-Clock; zwei Varianten mit unterschiedlichen Easing-Kurven passen Frame für Frame.
  • PixiJS / Live2D: Seite muss nicht geändert werden. Pixi-Ticker verwendet rAF und performance.now, folgt der Fake-Clock; die Physik wird mit dieser Zeit berechnet. Die Cubism-Laufzeit von Live2D (live2dcubismcore.min.js) muss mit einem normalen <script> vor den Modulen geladen werden; die Engine prüft dies beim Import und gibt sonst sofort einen Fehler aus. Getestet: Hiyori mit Physik an/aus, zwei Aufnahmen mit 75 Frames byteweise identisch.
  • three.js / VRM: Seite muss nicht geändert werden. Die eigene rAF-Schleife folgt der Fake-Clock (getestet: VRM-Schreibtischhaustier-Seiten konnten direkt aufgenommen werden, Unterschiede an/aus sind sichtbar). Zum Auslesen von Knochenwinkeln pro Frame auf hook umstellen, siehe references/hook.md.
  • Spiele, andere Canvas-Anwendungen: Wie oben. Wenn die Anwendung setTimeout für die Spielschleife verwendet, ist das ebenfalls von der Fake-Clock abgedeckt.

6. Lizenzen und Datenschutz

  • Bei fremden Modellen und Assets muss die Lizenz beachtet werden: bei Verbot der Weiterverbreitung (z. B. einige VN3-Klauseln) darf das GIF nur zur eigenen Einsicht und zum Vorzeigen verwendet werden – nicht in öffentliche Repos oder öffentlich veröffentlichen. Wenn Nutzung erlaubt ist, aber der Autor genannt werden muss, die Autorenangabe bei Veröffentlichung ergänzen.
  • GIFs und Einzelbilder nicht in git ablegen (dafür ein lokales Verzeichnis im Projekt oder im temp-Ordner), Testseiten nach Gebrauch löschen.

7. Verhältnis zu anderen Skills

  • Statische Screenshots, mehrere Viewports, Dark Mode: frontend-verification (dessen shot.mjs verwendet echte Zeit; für Animationen dieses Skill verwenden).
  • Werbeclips mit Musik als MP4: motion-showreel.
  • Wie man Charakter-Animationen natürlich macht (Bewegungsebenen, Physik, Clips): live2d-vrm-animation.

8. Quelle und Wartung

  • Quelle: Cirra (Schreibtischhaustier, RedCarpG/cirra-deskpet) – 2026-11-10 entwickelt, um 3D-Charaktere mit Bewegungsebenen, Hüfte/Beine, Haar-Schwung aufzunehmen und dem Benutzer den Vorher-Nachher-Vergleich zu zeigen. Die damalige Vorgehensweise stand in diesem Repository unter docs/vrm-motion.md §11–§12 und docs/vrm-spring-force.md §8 („Wie überprüfen“). Die Erfahrungen sind in einem privaten Repository unter lessons/animation-capture.md festgehalten (der 3D-Charakter-Teil auch in lessons/threejs-vrm-animation.md).
  • Ursprung: RedCarpG/redcarpg-skills unter skills/animation-capture/. Die Kopie in ~/.claude/skills/ wurde durch Installation erzeugt – nicht direkt darin ändern.
  • Wie geändert wird:
    • In redcarpg-skills die Änderung vornehmen, per Pull Request; in manifest.json die Version erhöhen, node scripts/validate.mjs durchlaufen lassen und dann einen Tag setzen (animation-capture-v<Version>), danach die lokale Kopie neu installieren.
    • Nach einer Änderung an den Skripten einmal auf einer CSS-Seite, einer Live2D- oder three.js-Seite aufnehmen und bestätigen, dass zwei Varianten Frame für Frame übereinstimmen; dieselbe Variante zweimal aufnehmen ergibt identische Bilder (Vergleich mit Pills ImageChops.difference).
    • Bei geänderten Verfahren oder Fallstricken die lessons-Dateien aktualisieren; Testfälle in evals/.
    • Unten eine Zeile mit Änderungshistorie anfügen.
  • Änderungshistorie:
    • 2026-11-12: 1.0.0, in redcarpg-skills als öffentliches Release veröffentlicht. Lokale absolute Pfade entfernt, Inhalt unverändert.
    • 2026-11-10, Cirra: Erste Version. record.mjs (shim- und hook-Taktgeber), clock-shim.js, make_gif.py. Getestet auf Webseiten (CSS-Übergänge, CSS-Animationen, WAAPI, rAF), Live2D (Hiyori, Physik an/aus), three.js VRM (mit Bewegungsebenen an/aus).
    • 2026-11-10, Cirra: Beim Testen festgestellt, dass CSS/WAAPI-Animationen, die beim Seitenladen starten, bevor die Fake-Clock aktiv ist, mit echter Zeit um einige Millisekunden vorauslaufen – zwei Varianten unterscheiden sich um 16.6 ms. Timer-gesteuerte Übergänge warten, bis dieser Schritt abgeschlossen ist (50 ms werden zu 80 ms). Behoben: Die ersten Animationen werden ab 0 gezählt, und nach jedem Timer-Durchlauf, rAF und jedem Schritt wird neu geprüft. Nach der Korrektur: ein 50 ms später gestarteter 600 ms-Übergang war bei 80 ms genau bei 8.0 px, beide Varianten hatten den gleichen Frame 0. Zwei Testfälle (Web-Easing-Vergleich, springende Ball-Verformung) bestehen sowohl mit als auch ohne Skill – der Skill spart das jedesmalige Neuschreiben der Aufnahmeskripte sowie die bereits gefundenen Fallstricke.
    • 2026-11-10, Cirra: Haar-Schwung (Zentrifugalkraft, nachgezogen) als Vergleichs-GIF aufgenommen, Yumeka und Sendagaya Shino; 3D-Charaktere mit Drehteller (Charakter in eine drehbare Group) und setInterval der Fake-Clock, ohne Aufnahmecode im Seiten-Skript.
    • 2026-11-10: Nach den neuen Formatierungsregeln „Wartung“ in „Quelle und Wartung“ umbenannt.
    • 2026-11-10, Cirra: Benutzer stellten fest, dass Chrome bei 3D-Aufnahmen dauerhaft viel CPU frisst. record.mjs geändert: Standardmäßig GPU-Rendering (--render gpu; --render cpu bleibt SwiftShader), am Anfang wird das verwendete Rendering ausgegeben. Chrome löscht beim Beenden sein Konfigurationsverzeichnis, was EPERM verursachen kann – jetzt wird der Fehler ignoriert, falls das Löschen nicht klappt. Getestet: Auf einer RTX 3060 Ti sank die Shader-Last eines Frames von 154 ms auf 8 ms; GPU-Rendering ergab bei zwei Aufnahmen 10 Frames byteweise identisch. GPU- und Software-Rendering unterscheiden sich Frame für Frame (Kanten), daher nicht gemischt vergleichen.

Individual skills in this repo

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

Verwandte Skills