Extraction de mouvement de vidéo & prototype (video-to-gsap)
1. Objectif & Déclencheurs
Les micro-interactions fluides et les mouvements cinématiques différencient les expériences de commerce haut de gamme. Cependant, des animations web mal implémentées provoquent des blocages de thread, des pertes d'images (jank), des fuites mémoire et une mauvaise Interaction to Next Paint (INP). Cette compétence régit l'extraction et l'implémentation d'animations fluides à 60/120 FPS.
Déclencheurs :
- Lorsque l'utilisateur fournit un clip vidéo, un GIF ou un prototype de mouvement Figma.
- Lors de l'animation de paniers coulissants, de transitions de pages, d'extensions d'accordéons, de survols de cartes produits ou de célébrations de finalisation de commande.
- Lors de la création d'animations pilotées par le défilement avec ScrollTrigger.
2. Protocole de déconstruction du mouvement
Lors de l'analyse d'un clip de mouvement, identifiez systématiquement :
- La séquence chorégraphique :
- Mise en scène / Déclencheur : Quelle action déclenche le mouvement (survol, défilement, changement de route, clic) ?
- Anticipation : Y a-t-il un contre-mouvement subtil avant le mouvement principal ?
- Action et déplacement : Quels éléments se déplacent, dans quelles directions, sur quelle distance (px, rem ou %) ?
- Stabilisation et suivi : Le mouvement dépasse-t-il sa cible (ressort/élastique) ou ralentit-il en douceur (
power3.out,expo.out) ?
- Analyse temporelle :
- Durée de chaque élément (ex. entrée du tiroir :
450ms, fondu d'arrière-plan :300ms). - Décalage entre les enfants décalés (ex. cartes d'une grille produit :
0.06sde décalage).
- Durée de chaque élément (ex. entrée du tiroir :
- Signature d'assouplissement :
- Linéaire : strictement pour les boucles continues (ex. marquee).
- Ease-out (
power2.out,expo.out) : pour les entrées d'interface (entrée rapide, stabilisation douce). - Ease-in (
power2.in) : pour les sorties d'interface (accélération vers la sortie). - Ease-in-out (
power3.inOut) : pour les transformations d'état restant dans la vue.
3. Normes de performance et d'accélération matérielle
Règle 1 : Animer uniquement les propriétés composites
- Autorisé :
transform(x,y,xPercent,yPercent,scale,rotation,skew) etopacity(ou GSAPautoAlpha). - Strictement interdit : Animer les propriétés de layout ou de peinture (
width,height,top,left,right,bottom,margin,padding,border-width). Celles-ci forcent le CPU du navigateur à recalculer la géométrie de mise en page et à repeindre les pixels à chaque image, entraînant une dégradation de l'INP et des pertes d'images.
Règle 2 : Zéro fuite mémoire avec useGSAP limité à un scope
- Utilisez toujours le package officiel
@gsap/reactet le hookuseGSAP. - Fournissez une référence
scopeconteneur. Tous les sélecteurs à l'intérieur seront automatiquement limités, et toutes les timelines, tweens et instances ScrollTrigger seront proprement annulées et tuées lorsque le composant se démonte. - Cela empêche les écouteurs d'événements zombies et la rétention mémoire dans les applications monopages et les cycles de rechargement rapide de React 19.
Règle 3 : Éliminer le FOUC (Flash de contenu non stylisé)
- Utilisez
autoAlphaau lieu deopacity.autoAlphadéfinitvisibility: hiddenlorsque l'opacité atteint 0, empêchant les clics fantômes et les flashs de rendu avant l'hydratation. - Alternativement, déclarez des états cachés par défaut CSS et révélez-les avec
gsap.fromTo.
4. Plan de mise en œuvre en production
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
// Assurez-vous que les plugins sont enregistrés de manière sécurisée
if (typeof window !== "undefined") {
gsap.registerPlugin(useGSAP, ScrollTrigger);
}
interface ProductShowcaseProps {
readonly items: Array<{ id: string; title: string; price: string }>;
}
export function ProductShowcase({ items }: ProductShowcaseProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
useGSAP(
() => {
// Timeline d'entrée accélérée matériellement
const tl = gsap.timeline({
defaults: {
ease: "expo.out",
duration: 0.8,
},
});
tl.from(".showcase-header", {
autoAlpha: 0,
y: 30,
clearProps: "all", // Nettoie les styles en ligne après l'animation
}).from(
".product-card",
{
autoAlpha: 0,
y: 40,
scale: 0.96,
stagger: 0.08,
duration: 0.7,
clearProps: "transform,opacity",
},
"-=0.4" // Chevauchement transparent
);
},
{ scope: containerRef } // Limite de nettoyage basée sur le scope
);
return (
<section ref={containerRef} className="w-full py-16 px-6">
<div className="showcase-header max-w-4xl mx-auto text-center mb-12">
<h2 className="text-h1 font-bold text-content-primary">Collection vedette</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-7xl mx-auto">
{items.map((item) => (
<article
key={item.id}
className="product-card will-change-transform bg-surface-elevated rounded-lg p-6 border border-border-subtle"
>
<h3 className="text-h3 font-semibold text-content-primary">{item.title}</h3>
<p className="text-content-secondary mt-2">{item.price}</p>
</article>
))}
</div>
</section>
);
}
5. Conformité au mouvement réduit
Respectez toujours les utilisateurs qui préfèrent un mouvement réduit :
const { contextSafe } = useGSAP({ scope: containerRef });
// Vérifier la préférence d'accessibilité
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (prefersReducedMotion) {
// Afficher instantanément l'état final de repos sans animation
gsap.set([".showcase-header", ".product-card"], { autoAlpha: 1, y: 0, scale: 1 });
return;
}