5.7 KiB
| name | description |
|---|---|
| motion-spring-physics | 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 :
- Vous animez des éléments interactifs (boutons, modales, tiroirs, onglets, menus accordéons).
- Vous concevez des transitions d'état de liste ou de réorganisation (glisser-déposer, suppression, filtrage).
- Vous implémentez un indicateur de sélection flottant ou un curseur d'onglets partagé (
layoutId). - Vous créez des micro-interactions au survol ou à la pression tactile (
whileHover,whileTap). - 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 :
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 :
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 :
/* 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
transformetopacity.