Communitygithub.com

joo6077/react-animation

Bietet eine Übersicht über animationsbezogene Fallstricke, Tools und Prozesse für React-Entwickler, insbesondere für Claude-Code-Plugins und ähnliche KI-Assistenten.

Was ist react-animation?

Dieses Skill-Dokument definiert klare Richtlinien für die Implementierung von Animationen in React-Projekten ohne externe Bibliotheken. Es priorisiert eine tier-basierte Vorgehensweise (Tier 1: Tailwind/CSS, Tier 2: View Transitions API, Tier 3: Custom Pointer Primitives), um die einfachste Lösung zuerst zu versuchen. Wichtige Fallstricke wie das Verbot von Animationsbibliotheken (z.B. framer-motion, react-spring), die Notwendigkeit von `touch-action: none` für mobile Drag-Interaktionen und die Behandlung von `pointercancel` werden hervorgehoben. Das Dokument enthält zudem spezifische Anweisungen für KI-Agenten, wie z.B. die Verwendung von `view-transition-name: match-element` (ab Chrome 137) und die Unterstützung von `prefers-reduced-motion`. Es wird für Entwickler empfohlen, die mit KI-Codierungsagenten wie Claude Code, Cursor oder Codex arbeiten, um konsistente und barrierefreie Animationen in React-Anwendungen zu gewährleisten.

Funktioniert mit✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/joo6077/claude-plugins/tree/HEAD/react-kit/skills/react-animation

In Ihrer bevorzugten KI fragen

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

Dokumentation

React Animation

Die Plugin-Installation enthält kein docs/react/ Verzeichnis. Wenn der Pfad docs/... in dieser Datei nicht geöffnet werden kann, hänge denselben Pfad an https://raw.githubusercontent.com/joo6077/claude-plugins/main/ an und versuche, die Datei zu lesen. Wenn das auch nicht funktioniert, schreibe, dass die Datei nicht gelesen werden konnte, und erfinde keinen Inhalt.

Fallstricke

  1. Null-Bibliotheken-Prinzip – absolut keine Ausnahmen: Motion (framer-motion) / dnd-kit / react-spring / react-transition-group / @formkit/auto-animate / react-dnd / gsap / lottie-react / react-beautiful-dnd / animate.css sind verboten (Installation und Import). Wenn Import-Anweisungen für diese Bibliotheken in der Codebasis vorhanden sind, gibt /react-audit einen Build-Fehler aus. Auch auf Anfrage des Benutzers hin eine alternative Implementierung vorschlagen und die Verwendung der Bibliothek verweigern.

  2. Immer mit der niedrigsten Stufe beginnen: Bei einer "Animation"-Anfrage wird oft fälschlicherweise sofort Stufe 3 verwendet. Befolge die Bewertungsregeln und prüfe zuerst, ob Stufe 1 möglich ist. Nur wenn nicht, gehe zu einer höheren Stufe über.

  3. tailwindcss-animate Plugin prüfen: Erweiterte Utility-Klassen wie animate-in, fade-in-*, slide-in-from-* benötigen tailwindcss-animate. Es ist standardmäßig in shadcn/init-Projekten enthalten. Wenn nicht vorhanden, führe pnpm add -D tailwindcss-animate aus und weise auf die Plugin-Registrierung in tailwind.config.ts hin.

  4. Eindeutigkeit von view-transition-name: Wenn zwei Elemente gleichzeitig denselben Namen haben, tritt ein "duplicate name"-Fehler auf. Verwende unbedingt dynamische IDs (z. B. view-transition-name: task-${item.id}), um die Eindeutigkeit zu gewährleisten.

  5. pointercancel-Behandlung erforderlich: Wenn das pointercancel-Ereignis nicht behandelt wird, kann der Drag-Zustand bei Tab-Wechsel, Fokus auf eine andere App oder beim Unmounten von Elementen undicht werden. Setze im onPointerCancel-Handler den FSM unbedingt auf idle zurück.

  6. Fehlendes touch-action: none verhindert Mobile-Drag: Füge zu pointer-event-basierten Drag-Elementen unbedingt die Tailwind-Klasse touch-none hinzu, um das standardmäßige Scrollen/Zoomen des Browsers zu blockieren.

  7. Zustand-Drag-Store ist eine globale, einzelne Instanz: Erstelle keinen separaten Drag-Store pro Feature. src/presentation/shared/stores/drag-store.ts wird von allen Features gemeinsam genutzt.

  8. prefers-reduced-motion nicht angewendet: In allen Stufen müssen Animationen mit @media (prefers-reduced-motion: reduce) oder der Tailwind-Variante motion-reduce:* deaktiviert werden. Bei Fehlen gibt /react-audit eine Warnung aus.

  9. Animationen sind nur für die Präsentationsschicht: Importiere keine pointer events, view transitions oder den Zustand-Drag-Store in den Schichten domain/ oder data/. Animationsbezogener Code befindet sich ausschließlich in src/presentation/.

  10. Firefox unterstützt keine types-Parameter: Die Option types in document.startViewTransition({ update, types: [...] }) wird Stand 2026 nur stabil in Chromium-basierten Browsern unterstützt. Behandle den Fallback im withViewTransition-Wrapper mit try/catch.

  11. Browser-Unterstützung für scroll-gesteuerte Animationen (Stand 13.08.2026): animation-timeline: scroll() / scroll-timeline haben laut Can I Use eine globale Abdeckung von 85,43% – Chrome/Edge 115+, Safari/iOS 26+, Firefox 156+. Die Aussage, dass Firefox hinter einem Flag steckt, ist veraltet. Allerdings ist view() laut MDN noch nicht Baseline. Daher bleibt die Richtlinie bestehen, diese mit @supports (animation-timeline: scroll()) / @supports (animation-timeline: view()) zu umschließen und bei fehlender Unterstützung auf statische Styles zurückzugreifen.

  12. Nutzung von view-transition-name: match-element (Chrome 137+): Anstatt Dutzenden von Elementen manuell eindeutige Namen zu geben, kann der CSS-Wert view-transition-name: match-element verwendet werden, damit der Browser die Elemente automatisch zuordnet. Da dies Stand Q2 2026 jedoch Chromium-exklusiv ist, einen Fallback-Pfad beibehalten.

  13. Bewegung wird nicht durch statischen Code nachgewiesen – Nachweis vor Abschlusserklärung erforderlich (E2): Das Ergebnis dieses Skills kann seine Existenz nur durch Abspielen bestätigen. Die Tatsache, dass eine animate-*-Klasse vorhanden ist oder der Code startViewTransition aufruft, ist ein R3 (statischer) Nachweis und kein Beweis dafür, dass die Animation tatsächlich abgespielt wurde – nicht definierte Keyframes, dauerhaft zutreffendes prefers-reduced-motion, Abschneiden durch overflow: hidden des übergeordneten Elements und das Betreten des Fallbacks eines nicht unterstützten Browsers führen alle still dazu, dass "nichts passiert". Fülle kurz vor Fertigstellung die Checkliste aus §4 von react-kit/references/render-evidence-protocol.md. Bei der Überprüfung durch Snapshot-Vergleich markiere zwei Zeitpunkte (vorher/nachher) – ein einzelner Standbild-Frame liefert für jede Bewegung das gleiche Ergebnis und ist daher kein Orakel (§3 d). Wenn kein Nachweis erbracht werden kann, füge [Ungeprüft] hinzu und fülle die vier Felder (Hindernis · versuchte Umgehung · Grund für fehlende Kontrolle · Befehl zur erneuten Überprüfung – Protokoll §2) aus, um als teilweise abgeschlossen zu melden.

    Führe keine Animationsbibliothek ein, um einen Nachweis zu erbringen. Die Library Policy (Fallstrick #1) steht über dieser Regel und wird durch keine Überprüfungsbequemlichkeit gelockert.

  14. Die Basisaufnahme wird vor der Bearbeitung gemacht – Führe §1 Schritt 0 und den Vergleichswiederholungszyklus aus §2, Punkt 1 von react-kit/references/render-evidence-protocol.md vor der ersten Bearbeitung aus und hinterlasse das Ergebnis (Wiederholung, Konventionstabelle, aktuellen Screenshot-Pfad und zu ändernde Markierung) in der Antwort. Nach der Bearbeitung kann der Zustand vor der Bearbeitung nicht erneut aufgenommen werden.

  15. Tier 2 wird nach Update-Typ unterschieden – <ViewTransition> in React 19.3 ist eine stabile API (Hinzugefügt am 26.09.2026): <ViewTransition>, das von react importiert wird, ist in 19.3 keine experimentelle API mehr, sondern eine stabile API. Es wird ausgelöst, wenn sich der Stil von umschlossenen Kindern durch als Transition markierte Aktualisierungen (startTransition-Aktualisierung, Suspense Reveal, useDeferredValue) ändert oder sie gemountet/unmountet werden, und funktioniert nur im DOM (Quelle: https://react.dev/blog/2026/09/09/react-19-3: „We shared it as an experimental API last year, and in 19.3 it's stable and ready to use."). Verwende daher <ViewTransition> für als Transition markierte React-Zustandsaktualisierungen und den §3.1 withViewTransition-Wrapper für andere DOM-Aktualisierungen (nicht als Transition markierte synchrone Aktualisierungen, DOM-Manipulationen außerhalb von React). Entferne den Wrapper nicht. Da es sich um eine Komponente im react-Paket handelt, verstößt sie nicht gegen das Null-Bibliotheken-Prinzip (#1). Wenn das react-Paket des Projekts älter als 19.3 ist, verwende nur den Wrapper.

Prozess

1. Automatische Tier-Bewertung

Wenn kein --tier-Flag gesetzt ist, analysiere die Benutzeranfrage und wähle die niedrigste Stufe aus.

Schlüsselwort / SzenarioBewertete Stufe
"Einblenden", "Hochschieben", "Skalieren", "Pulsieren", "Hüpfen", "Hover-Effekt", "Deckkraft", "Schimmern", "Eingangsanimation"T1
"Skeleton-Loading → Fertig-Übergang", "Button Hover", "Accordion", "Modal Öffnen/Schließen", "Zustandsänderung"T1
"Scroll-Animation", "scroll-gesteuert", "Parallax", "Scroll-Fortschrittsbalken", "scroll-basiert"T1
"Seitenübergang", "geteiltes Element", "Grid zu Board", "View-Übergang", "Routenübergang", "DOM-Strukturänderung"T2
"scroll-ausgelöst", "Beim Scroll-Kreuzungspunkt auslösen", "Animation an bestimmter Position starten"T2
"Drag & Drop", "Sortierbare Liste", "sortierbar", "Kanban", "Ziehen", "Ablegen", "Geste", "Pfeil-Verbindungslinie"T3

Wenn die Grenze zwischen T2/T3 unklar ist, konsultiere den animation-architect-react-Agenten.

3-Stufen-Zusammenfassung:

StufeWerkzeugAnwendungsszenarioSchwierigkeitsgrad
T1Tailwind animate-* + CSS @keyframes + scroll-gesteuertZustandsänderung, Hover, einfache Schleife, Scroll-KopplungNiedrig
T2View Transitions API (withViewTransition-Wrapper) · React 19.3 <ViewTransition> (nur als Transition markierte Aktualisierungen – Fallstrick 15)View-/Routenübergang, geteiltes Element, FLIPMittel
T3Pointer Events + FSM + requestAnimationFrameDrag & Drop, Geste, SVG-VerbindungslinieHoch

2. Stufe 1 – Tailwind + CSS-Implementierung

2.1 Tailwind integrierte Utilities

KategorieUtilityBeispiel
Übergangtransition-transform, transition-colors, transition-opacityhover:scale-105 transition-transform duration-200
Dauerduration-75~duration-1000duration-300
Beschleunigungease-linear, ease-in, ease-out, ease-in-outease-in-out
Animation (integriert)animate-spin, animate-ping, animate-pulse, animate-bounceLaden, Benachrichtigung
Verzögerungdelay-75~delay-1000Staffel-Effekt

2.2 tailwindcss-animate Erweiterungs-Utilities

Enter/Exit-Animationen bereitgestellt durch das tailwindcss-animate-Plugin:

  • animate-in / animate-out
  • fade-in-*, fade-out-*
  • slide-in-from-top/bottom/left/right-*, slide-out-to-*
  • zoom-in-*, zoom-out-*

Kombiniere mit data-[state=...] Attributselektoren für zustandsbasierte Animationen:

// Beispiel Drawer öffnen/schließen
<div
  data-state={open ? 'open' : 'closed'}
  className="
    data-[state=open]:animate-in data-[state=closed]:animate-out
    data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
    data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left
    duration-300
    motion-reduce:transition-none motion-reduce:animate-none
  "
>
  Drawer-Inhalt
</div>

2.3 Direkte CSS @keyframes-Deklaration

Deklariere benutzerdefinierte Animationen, die nicht von Tailwind-Utilities abgedeckt werden, im @theme-Block von globals.css:

/* src/presentation/styles/globals.css */
@import "tailwindcss";

@theme {
  --animate-wiggle: wiggle 1s ease-in-out infinite;
  --animate-shimmer: shimmer 2s linear infinite;

  @keyframes wiggle {
    0%, 100% { transform: rotate(-3deg); }
    50%      { transform: rotate(3deg); }
  }

  @keyframes shimmer {
    from { background-position: -200% 0; }
    to   { background-position: 200% 0; }
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Verwendung: <div className="animate-wiggle">...</div>

2.4 Beispiele für Stufe 1-Szenarien

Fade-in-Eingang:

<div className="animate-in fade-in-0 duration-500 motion-reduce:animate-none">
  Fade-in beim Seitenladen
</div>

Hover-Scale-Button:

<button className="transition-transform duration-200 hover:scale-105 active:scale-95 motion-reduce:transition-none">
  Knopf
</button>

Gestaffelter Listeneingang:

{items.map((item, i) => (
  <div
    key={item.id}
    className="animate-in slide-in-from-bottom-4 fade-in-0 motion-reduce:animate-none"
    style={{ animationDelay: `${i * 50}ms`, animationFillMode: 'backwards' }}
  >
    {item.name}
  </div>
))}

2.5 Scroll-gesteuerte Animationen (CSS-nativ)

Implementiere Animationen, die an die Scroll-Position gekoppelt sind, ohne JavaScript mit CSS animation-timeline. Stand 13.08.2026 werden scroll() / scroll-timeline in Chrome/Edge 115+, Safari/iOS 26+, Firefox 156+ unterstützt (Can I Use global 85,43%), und view() ist laut MDN noch nicht Baseline. Umschließe beide Fälle mit @supports für einen gracevollen Fallback in nicht unterstützten Browsern.

Scroll-Fortschrittsbalken:

/* globals.css */
@keyframes progress-grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
<div
  className="fixed top-0 left-0 h-1 w-full origin-left bg-primary motion-reduce:hidden"
  style={{
    animation: 'progress-grow linear',
    animationTimeline: 'scroll(root block)',
  }}
/>

Fade-in beim Elementeintritt (View Timeline):

@keyframes fade-slide-in {
  from { opacity: 0; transform: translateY(2rem); }
  to   { opacity: 1; transform: translateY(0); }
}

@supports (animation-timeline: view()) {
  .scroll-reveal {
    animation: fade-slide-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }
}
<section className="scroll-reveal motion-reduce:opacity-100">
  Beim Scrollen erscheinender Inhalt
</section>

Hinweis: Scroll-ausgelöste Animationen (Chrome 145, 2026) sind ein separates Konzept, das beim Kreuzen eines bestimmten Scroll-Offsets eine zeitbasierte Animation auslöst. Nicht mit scroll-gesteuert verwechseln. Scroll-ausgelöste Animationen werden als Kandidat für Stufe 2 eingestuft.

3. Stufe 2 – View Transitions API-Implementierung

Same-Document View Transitions sind laut MDN Baseline 2025, laut Can I Use global 90,2% – Chrome/Edge 111+, Safari 18+, Firefox 144+ (Stand 13.08.2026). Automatische FLIP-Animation bei DOM-Änderungen innerhalb einer SPA.

Hingegen sind @view-transition-basierte Cross-Document / MPA-Übergänge und einige Detailfunktionen noch limited availability. Verwende bei Routenübergängen standardmäßig den Same-Document-Pfad und behalte die Feature-Erkennung + Fallback bei (§6 Standardabdeckungslücke 8).

3.1 withViewTransition-Wrapper erstellen

Erstelle die Datei, falls nicht vorhanden:

// src/presentation/shared/lib/view-transition.ts
export function withViewTransition(
  updateDOM: () => void,
  options?: { types?: string[] },
): void {
  // prefers-reduced-motion respektieren
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    updateDOM()
    return
  }
  // Feature-Erkennung (Fallback für alte Browser)
  if (typeof document.startViewTransition !== 'function') {
    updateDOM()
    return
  }
  // Verzweigung für Unterstützung des types-Parameters (Firefox-Initialimplementierung nicht unterstützt)
  try {
    if (options?.types && options.types.length > 0) {
      document.startViewTransition({ update: updateDOM, types: options.types })
    } else {
      document.startViewTransition(updateDOM)
    }
  } catch {
    updateDOM()
  }
}

3.2 view-transition-name CSS für geteilte Elemente

Auch wenn sich die DOM-Struktur ändert, als "gleiches Element" identifizieren und sanft verschieben:

// Eindeutigkeit durch dynamische ID gewährleisten (doppelte Namen verboten)
<div
  className="rounded-lg border p-4"
  style={{ viewTransitionName: `task-${task.id}` }}
>
  {task.title}
</div>

3.3 Grid ↔ Board-View-Übergang (repräsentatives Szenario)

// src/presentation/features/tasks/components/task-switcher.tsx
import { withViewTransition } from '@/presentation/shared/lib/view-transition'
import { useTaskStore } from '../store'

export function TaskViewSwitcher() {
  const viewMode = useTaskStore((s) => s.viewMode)
  const setViewMode = useTaskStore((s) => s.setViewMode)

  return (
    <button
      onClick={() => {
        withViewTransition(() => {
          setViewMode(viewMode === 'grid' ? 'board' : 'grid')
        })
      }}
    >
      {viewMode === 'grid' ? 'Zur Board-Ansicht' : 'Zur Grid-Ansicht'}
    </button>
  )
}

export function TaskList({ tasks }: { tasks: Task[] }) {
  const viewMode = useTaskStore((s) => s.viewMode)
  return (
    <div className={viewMode === 'grid' ? 'grid grid-cols-3 gap-4' : 'flex gap-4 overflow-x-auto'}>
      {tasks.map((task) => (
        <div
          key={task.id}
          className="rounded-lg border p-4"
          style={{ viewTransitionName: `task-${task.id}` }}
        >
          {task.title}
        </div>
      ))}
    </div>
  )
}

3.4 CSS-Übergangs-Feinabstimmung

/* globals.css – Übergangsdauer + Beschleunigung */
::view-transition-group(*) {
  animation-duration: 0.4s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Individuelle Anpassung für bestimmte Elemente */
::view-transition-group(hero-image) {
  animation-duration: 0.6s;
}

/* Sofortiger Übergang bei reduzierter Bewegung */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*) {
    animation-duration: 0.01ms;
  }
}

4. Stufe 3 – Implementierung von benutzerdefinierten Pointer-Primitiven

Direkte Implementierung ohne Bibliotheken unter Verwendung der Pointer Events API + Zustandsmaschine (FSM).

4.1 useDrag-Hook erstellen

// src/presentation/shared/hooks/use-drag.ts
import { useCallback, useEffect, useReducer, useRef } from 'react'

// FSM 3 Zustände: idle / dragging / dropping
type DragState =
  | { kind: 'idle' }
  | { kind: 'dragging'; startX: number; startY: number; offsetX: number; offsetY: number; pointerId: number }
  | { kind: 'dropping'; offsetX: number; offsetY: number }

type DragAction =
  | { type: 'start'; x: number; y: number; pointerId: number }
  | { type: 'move'; x: number; y: number }
  | { type: 'drop' }
  | { type: 'resolved' }
  | { type: 'cancel' }

function dragReducer(state: DragState, action: DragAction): DragState {
  switch (action.type) {
    case 'start':
      return { kind: 'dragging', startX: action.x, startY: action.y, offsetX: 0, offsetY: 0, pointerId: action.pointerId }
    case 'move':
      if (state.kind !== 'dragging') return state
      return { ...state, offsetX: action.x - state.startX, offsetY: action.y - state.startY }
    case 'drop':
      if (state.kind !== 'dragging') return state
      return { kind: 'dropping', offsetX: state.offsetX, offsetY: state.offsetY }
    case 'resolved':
    case 'cancel':
      return { kind: 'idle' }
  }
}

type DragHandlers = {
  onPointerDown: (e: React.PointerEvent) => void
  onPointerMove: (e: React.PointerEvent) => void
  onPointerUp: (e: React.PointerEvent) => void
  onPointerCancel: (e: React.PointerEvent) => void
}

export function useDrag(): {
  state: DragState
  handlers: DragHandlers
  ref: React.RefObject<HTMLElement | null>
  resolve: () => void
} {
  const [state, dispatch] = useReducer(dragR

… (gekürzt für die Übersetzung)

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

Verwandte Skills