Was macht Motion Foundations?
Die Basisschicht des Motion-Systems. Definiert jeden Wert, jede Einschränkung und
jede Regel, die von nachgelagerten Skills (motion-patterns, motion-advanced) geerbt werden.
Laden Sie diesen Skill, bevor Sie mit Animationsarbeiten beginnen.
Wann aktivieren
- Starten einer animierten Komponente von Grund auf
- Einrichten von Token, Feder-Presets oder Easing-Werten
- Implementieren von
prefers-reduced-motion-Unterstützung - Debuggen von Hydration-Konflikten durch Animations-Initialzustände
- Bewerten, ob eine Animation überhaupt existieren sollte
Ausgaben
Dieser Skill produziert:
- Ein gemeinsames
motionTokens-Objekt (Dauer, Easing, Distanz, Skalierung) - Eine gemeinsame
springs-Voreinstellungs-Map (5 benannte Konfigurationen) - Eine
shouldAnimate()-Funktion, die von allen Komponenten verwendet wird - Barrierefreie Animations-Standards via
useReducedMotion - SSR-sichere Initialzustände ohne Hydration-Warnungen
Prinzipien
Motion muss mindestens eines der folgenden Kriterien erfüllen, sonst muss es entfernt werden:
- Aufmerksamkeit lenken
- Zustand kommunizieren
- Räumliche Kontinuität bewahren
Reaktionsgeschwindigkeit hat immer Vorrang vor Laufruhe. Eine 60-fps-Animation, die Eingabeverzögerungen verursacht, ist schlimmer als keine Animation.
Regeln
Diese sind nicht verhandelbar. Sie gelten für jede Komponente im System.
- Nur
motion/reactverwenden. Niemals vonframer-motionimportieren. Niemals beide im selben Baum mischen. initialmuss mit der Serverausgabe übereinstimmen. Wenn der Serveropacity: 1rendert, muss dieinitial-Eigenschaft ebenfallsopacity: 1sein. Keine Ausnahmen.- Reduced Motion überschreibt alles. Wenn
useReducedMotion()truezurückgibt oderprefersReducedtrueist, werden alle Transformationen deaktiviert. Ausschließlich Opacity-Fades mit ≤ 0,2 s sind der einzig erlaubte Fallback. - Niemals Layout-Eigenschaften animieren.
width,height,top,left,margin,paddingsind inanimateverboten. Verwenden Sie nurtransformundopacity. - Alle Token-Werte stammen aus
motionTokens. Fest codierte Dauern und Easings in Komponentendateien sind verboten. - Alle Feder-Konfigurationen stammen aus der
springs-Map. Inline-stiffness/damping-Werte sind verboten. "use client"ist erforderlich in jeder Datei, die vonmotion/reactimportiert.- Niemals
windowodernavigatorauf Modulebene lesen. Immer mittypeof window !== "undefined"schützen.
Entscheidungshilfe
Auswahl einer Dauer
| Token | Verwenden, wenn |
|---|---|
instant | Tooltip ein-/ausblenden, Fokusring, Badge-Update |
fast | Button-Feedback, Icon-Wechsel, Chip-Umschalten |
normal | Modal öffnen, Karte erweitern, Seitenelement eintreten |
slow | Hero-Eingang, seitenübergreifender Übergang |
crawl | Gezieltes Storytelling; sparsam verwenden |
Auswahl einer Feder
| Voreinstellung | Verwenden, wenn |
|---|---|
snappy | Standard-UI – Buttons, Chips, Navigations-Items |
gentle | Karten, Modals, Panels sanft landen |
bouncy | Verspielte Momente – leere Zustände, Onboarding |
instant | Tooltips, Popover, Dropdowns |
release | Drag-Freigabe – natürliches physikalisches Gefühl |
Wann Animation vollständig deaktivieren
Deaktivieren (shouldAnimate() gibt false zurück), wenn:
prefersReducedtrueistisLowEndtrueist und die Animation nicht essentiell ist- Das Element außerhalb des Bildschirms ist und niemals in den Viewport gelangt
- Die Animation rein dekorativ ist und keinen UX-Zweck erfüllt
Kernkonzepte
Token-System
// lib/motion-tokens.ts
export const motionTokens = {
duration: {
instant: 0.08,
fast: 0.18,
normal: 0.35,
slow: 0.6,
crawl: 1.0,
},
easing: {
smooth: [0.22, 1, 0.36, 1],
sharp: [0.4, 0, 0.2, 1],
bounce: [0.34, 1.56, 0.64, 1],
linear: [0, 0, 1, 1],
},
distance: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 48,
},
scale: {
subtle: 0.98,
press: 0.95,
pop: 1.04,
},
}
export const springs = {
snappy: { type: "spring", stiffness: 300, damping: 30 },
gentle: { type: "spring", stiffness: 120, damping: 14 },
bouncy: { type: "spring", stiffness: 400, damping: 10 },
instant: { type: "spring", stiffness: 600, damping: 35 },
release: { type: "spring", stiffness: 200, damping: 20, restDelta: 0.001 },
}
Laufzeit-Flags
// lib/motion-config.ts
export const motionConfig = {
isLowEnd() {
return (
typeof navigator !== "undefined" &&
navigator.hardwareConcurrency <= 4
)
},
prefersReduced() {
return (
typeof window !== "undefined" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches
)
},
shouldAnimate({ essential = false } = {}) {
if (this.prefersReduced()) return false
if (!essential && this.isLowEnd()) return false
return true
},
duration() {
return this.isLowEnd() || this.prefersReduced()
? motionTokens.duration.instant
: motionTokens.duration.normal
},
}
Barrierefreiheit
Prioritätsreihenfolge (höchste zu niedrigste):
prefers-reduced-motion: reduce– deaktiviert alle Transformationen, begrenzt Opacity-Übergänge auf ≤ 0,2 s- Erkennung von Geräten mit niedriger Leistung – reduziert Dauer, entfernt nicht-essentielle Animationen
- Design-Präferenz – alles andere
Motion muss sich elegant verschlechtern. Es darf niemals abrupt verschwinden und so Layout-Verschiebungen verursachen oder die Orientierung beeinträchtigen.
// hooks/use-reduced-motion.tsx
"use client"
import { useReducedMotion } from "motion/react"
export function useSafeMotion(fullY: number = 16) {
const reduce = useReducedMotion()
return {
initial: { opacity: 0, y: reduce ? 0 : fullY },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: reduce ? 0 : -fullY },
}
}
/* globals.css */
@media (prefers-reduced-motion: reduce) {
.motion-safe-transition { transition: opacity 0.15s; }
.motion-reduce-transform { transform: none !important; }
}
<!-- Tailwind -->
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
SSR-/Hydration-Sicherheit
Regel: initial muss immer mit dem übereinstimmen, was der Server rendert.
// FALSCH – Server rendert opacity:1, aber initial sagt 0 → Hydration-Konflikt
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
// RICHTIG – AnimatePresence verwenden oder auf Client-Mount warten
"use client"
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
<motion.div
initial={{ opacity: mounted ? 0 : 1 }}
animate={{ opacity: 1 }}
/>
Code-Beispiele
Durchgängig: Token + Federn + Barrierefreiheit + SSR-Schutz
// components/fade-in-card.tsx
"use client"
import { useState, useEffect } from "react"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"
import { motionConfig } from "@/lib/motion-config"
interface FadeInCardProps {
children: React.ReactNode
delay?: number
}
export function FadeInCard({ children, delay = 0 }: FadeInCardProps) {
// SSR-Schutz – initial muss mit der Serverausgabe übereinstimmen (opacity: 1)
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
// Barrierefreiheit – deaktiviert Transformationen, wenn reduzierte Bewegung bevorzugt wird
const safeMotion = useSafeMotion(motionTokens.distance.md)
// Geräte-Prüfung – Animation auf schwacher Hardware überspringen
if (!motionConfig.shouldAnimate() || !mounted) {
return <div>{children}</div>
}
return (
<motion.div
initial={safeMotion.initial}
animate={safeMotion.animate}
exit={safeMotion.exit}
transition={{
...springs.gentle,
delay,
}}
whileHover={{ scale: motionTokens.scale.pop }}
whileTap={{ scale: motionTokens.scale.press }}
>
{children}
</motion.div>
)
}
Einschränkungen / Nicht-Ziele
Dieser Skill deckt nicht ab:
- UI-Komponenten-Muster (Button, Modal, Stagger) → siehe
motion-patterns - Drag, Gesten, SVG, Text-Animationen, benutzerdefinierte Hooks → siehe
motion-advanced - CSS-Animationen oder Tailwind
animate-*-Klassen ohnemotion/react - Drittanbieter-Animationsbibliotheken (GSAP, anime.js, usw.)
- Motion-Design-Entscheidungen (wann animieren, was betonen) – das ist eine Designfrage, keine Code-Einschränkung
Anti-Patterns
| Anti-Pattern | Verstoß gegen Regel | Behebung |
|---|---|---|
import { motion } from "framer-motion" | Regel 1 | motion/react verwenden |
initial={{ opacity: 0 }} bei SSR-Komponente | Regel 2 | Mount-Schutz hinzufügen |
useReducedMotion-Prüfung überspringen | Regel 3 | useSafeMotion-Hook verwenden |
animate={{ width: "100%" }} | Regel 4 | scaleX-Transform verwenden |
transition={{ duration: 0.4 }} inline | Regel 5 | motionTokens.duration.normal verwenden |
{ stiffness: 300, damping: 30 } inline | Regel 6 | springs.snappy verwenden |
Fehlende "use client"-Direktive | Regel 7 | Am Anfang der Datei hinzufügen |
navigator.hardwareConcurrency auf Modulebene | Regel 8 | Mit typeof navigator !== "undefined" umschließen |
Verwandte Skills
motion-patterns– konsumiert die hier definierten Token und Federn, um Button-, Modal-, Stagger-, Seitenübergangs- und Scroll-Muster zu erstellen. Definiert keine Werte neu.motion-advanced– konsumiert die hier definierten Token und Federn für Drag-, SVG-, Text- und Gesten-Muster. FügtuseAnimate-Sequenzen und benutzerdefinierte Hooks auf dieser Grundlage hinzu.