Skills/motion-spring-physics/SKILL.md

145 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: motion-spring-physics
description: >-
Implement physical, tactile micro-interactions and layout transitions using Spring Physics
(Motion / Framer Motion, FLIP technique via layoutId, CSS @starting-style, View Transitions API)
with strict compliance with prefers-reduced-motion.
---
# Motion & Spring Physics Architecture
Ce skill régit l'animation interactive haute fidélité. Il remplace les animations rigides basées sur des durées fixes (`transition: all 0.3s ease`) par des **ressorts physiques réels** (*Spring Physics*), conférant aux interfaces un toucher organique, réactif et interruptible caractéristique des applications comme Linear, Raycast ou macOS.
---
## 🎯 Déclencheurs d'Activation
Activez ce skill dès que :
1. Vous animez des éléments interactifs (boutons, modales, tiroirs, onglets, menus accordéons).
2. Vous concevez des transitions d'état de liste ou de réorganisation (glisser-déposer, suppression, filtrage).
3. Vous implémentez un indicateur de sélection flottant ou un curseur d'onglets partagé (`layoutId`).
4. Vous créez des micro-interactions au survol ou à la pression tactile (`whileHover`, `whileTap`).
5. Vous devez garantir l'accessibilité des animations face au critère WCAG 2.3.3 (`prefers-reduced-motion`).
---
## 🏎️ 1. Les Principes de la Physique de Ressort (*Springs*)
Contrairement aux courbes de Bézier cubiques (`ease-in-out`) qui forcent un temps fixe indépendamment de la distance parcourue, un ressort physique calcule la trajectoire en temps réel :
- **Stiffness (Raideur)** : Vitesse de rappel du ressort. Une raideur élevée (300–500) rend le contrôle vif et immédiat.
- **Damping (Amortissement)** : Friction qui freine le mouvement. Un amortissement bien réglé (20–30) évite les oscillations oscillatoires excessives (« effet cartoon »).
- **Mass (Masse)** : Inertie de l'élément (0.5 à 1.0 par défaut).
### Les 3 Profils de Ressort Standards
| Profil | Cas d'Usage | Paramètres Recommandés |
| :--- | :--- | :--- |
| **Snappy / Micro-tactile** | Boutons, toggles, badges, chips, icones | `{ type: 'spring', stiffness: 500, damping: 30, mass: 0.5 }` |
| **Smooth / Layout** | Modales, volets latéraux, accordéons, cartes | `{ type: 'spring', stiffness: 350, damping: 28, mass: 0.8 }` |
| **Gentle / Page Transition** | Entrées de page, vues séquentielles | `{ type: 'spring', stiffness: 200, damping: 24, mass: 1.0 }` |
---
## 💫 2. Transitions Partagées avec `layoutId` (Technique FLIP)
Pour un indicateur d'onglet ou un sous-lignage qui glisse d'un bouton à l'autre sans saut visuel :
```tsx
import { motion } from 'framer-motion';
import { useState } from 'react';
const tabs = ['Vue d\'ensemble', 'Analytique', 'Paramètres'];
export const TabNavigation = () => {
const [activeTab, setActiveTab] = useState(tabs[0]);
return (
<nav className="flex gap-2 p-1.5 rounded-xl bg-[oklch(0.18_0.01_260)] border border-[oklch(1_0_0/0.08)]">
{tabs.map((tab) => {
const isActive = activeTab === tab;
return (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className="relative px-4 py-1.5 text-sm font-medium rounded-lg text-white/80 hover:text-white transition-colors"
>
{/* Pilule d'arrière-plan animée en FLIP automatique */}
{isActive && (
<motion.div
layoutId="active-indicator"
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
className="absolute inset-0 rounded-lg bg-[oklch(0.28_0.02_260)] shadow-sm"
/>
)}
<span className="relative z-10">{tab}</span>
</button>
);
})}
</nav>
);
};
```
---
## ♿ 3. Respect Impératif de `prefers-reduced-motion`
Chaque animation doit se désactiver instantanément pour les utilisateurs sensibles au mal des transports ou ayant activé la réduction de mouvement dans leur OS :
```tsx
import { motion, useReducedMotion } from 'framer-motion';
export const AccessibleCard = ({ children }: { children: React.ReactNode }) => {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
initial={shouldReduceMotion ? { opacity: 1, y: 0 } : { opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: -8 }}
transition={
shouldReduceMotion
? { duration: 0 }
: { type: 'spring', stiffness: 300, damping: 25 }
}
whileHover={shouldReduceMotion ? {} : { scale: 1.015 }}
whileTap={shouldReduceMotion ? {} : { scale: 0.985 }}
>
{children}
</motion.div>
);
};
```
---
## 🎨 4. Alternatives CSS Modernes (Sans Dépendance JS)
Pour les transitions simples au chargement d'éléments, privilégiez `@starting-style` et la View Transitions API :
```css
/* Entrée fluide pure CSS d'un dialog ou popover */
dialog[open] {
opacity: 1;
transform: scale(1);
transition: opacity 0.2s ease-out, transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), display 0.2s allow-discrete;
}
@starting-style {
dialog[open] {
opacity: 0;
transform: scale(0.96);
}
}
```
---
## 📋 Checklist de Conformité Motion
- [ ] Aucun mouvement décoratif continu en boucle (pas de floating infini qui distrait).
- [ ] Les micro-interactions au clic utilisent `active:scale-[0.98]` ou un ressort réactif.
- [ ] `useReducedMotion()` (ou la media query `@media (prefers-reduced-motion: reduce)`) ramène la durée à 0.
- [ ] Les animations de changement de layout (`layoutId`) ne causent aucun reflow de texte brutal.
- [ ] La performance d'animation cible 60fps/120fps en animant exclusivement `transform` et `opacity`.