Créer un hero React avec effet d'inclinaison 3D
Un hero React avec inclinaison 3D mappe la position du pointeur dans la carte vers des motion values rotateX et rotateY, les passe dans des springs Framer Motion (stiffness 200, damping 30), et les applique à une motion.div avec transformStyle:'preserve-3d' et une perspective parent de 1200px. La carte s'incline jusqu'à 8 degrés sur chaque axe, puis revient à plat quand le curseur sort.
- Stack : React 18 + Framer Motion 11 + Tailwind v4 + lucide-react, ~175 lignes.
- API clé : useMotionValue, useSpring, useTransform, aucune dépendance en dehors de framer-motion.
- Plage d'inclinaison de ±8 degrés sur les deux axes ; les réglages du spring sont ajustables selon le ressenti de marque.
- Accessible : le contenu est du HTML standard sans couches cachées ; l'inclinaison est décorative et ne modifie pas l'ordre de tabulation.
- Sur mobile, la carte reste plate, l'inclinaison ne se déclenche pas sans souris.
Hero 3D Tilt est un hero centré construit autour d'une carte flottante unique qui pivote en trois dimensions au passage du curseur. L'inclinaison suit le pointeur avec de la physique de spring, pas un simple mappage linéaire, ce qui rend la réponse lourde et naturelle. Il convient aux produits SaaS et aux sites d'agences qui veulent une première impression tactile sans recourir à la vidéo plein écran ni à WebGL.
Anatomie
La section extérieure pose une perspective CSS de 1200px sur le viewport, donnant sa profondeur à la scène 3D. À l'intérieur se trouve un conteneur centré, et dedans une seule motion.div (la carte) avec transformStyle:'preserve-3d', c'est là que rotateX et rotateY sont appliqués. La carte contient trois couches visuelles superposées par z-index : un panneau de fond en verre (bordure + fond alt), un dégradé radial flou en guise de halo d'accentuation en haut, et le contenu réel (badge optionnel, h1 avec un mot en italique accentué, description, et un bouton CTA en pilule). Toutes les couleurs référencent des tokens CSS custom properties, donc la carte s'adapte à n'importe quel preset de thème.
Comment ça marche
La technique enchaîne trois hooks Framer Motion. useMotionValue maintient mouseX et mouseY en tant que flottants normalisés dans l'intervalle [-0.5, 0.5], dérivés de la position du curseur par rapport à la boîte englobante de la carte. useTransform convertit chaque valeur en degrés : mouseY mappe vers rotateX (l'avant s'incline vers le haut quand le curseur est en haut) et mouseX vers rotateY (le bord gauche se soulève quand le curseur va à droite). Les deux valeurs dérivées passent ensuite dans useSpring avec stiffness 200 et damping 30, ajoutant de l'inertie pour que la carte se stabilise plutôt que de claquer. Au mouseleave, les deux motion values reviennent à 0, et le spring ramène la carte à sa position plate de repos.
Comment le coder en React
Pose la perspective sur le parent
La perspective CSS doit être sur un élément parent, pas sur celui qui pivote. Ajoute perspective:'1200px' en style inline sur la section extérieure. Une valeur entre 800px et 1500px donne une profondeur convaincante sans déformer la carte au tilt maximum.
<section style={{ perspective: "1200px" }}> <motion.div style={{ rotateX, rotateY, transformStyle: "preserve-3d" }}> {/* card content */} </motion.div> </section>Suis le pointeur en coordonnées normalisées
À chaque événement mousemove, lis getBoundingClientRect depuis le ref de la carte, puis divise le décalage du curseur par la largeur et la hauteur de la carte. Soustraire 0.5 centre l'intervalle à zéro, donc la carte est plate quand le curseur est au milieu et s'incline sur les bords.
function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) { const rect = cardRef.current?.getBoundingClientRect(); if (!rect) return; const x = (e.clientX - rect.left) / rect.width - 0.5; const y = (e.clientY - rect.top) / rect.height - 0.5; mouseX.set(x); mouseY.set(y); }Mappe les coordonnées vers une rotation amortie par spring
Passe mouseX et mouseY par useTransform pour les mettre à l'échelle en degrés, puis entoure chaque résultat de useSpring. La stiffness contrôle la vitesse à laquelle la carte suit le pointeur ; le damping contrôle le dépassement avant stabilisation. Une stiffness de 200 avec un damping de 30 est réactive sans être saccadée.
const rotateX = useSpring(useTransform(mouseY, [-0.5, 0.5], [8, -8]), { stiffness: 200, damping: 30, }); const rotateY = useSpring(useTransform(mouseX, [-0.5, 0.5], [-8, 8]), { stiffness: 200, damping: 30, });Réinitialise au départ du curseur
Remets mouseX et mouseY à 0 dans le handler onMouseLeave. Comme les valeurs dérivées rotateX/rotateY sont enveloppées dans des springs, la carte revient à plat automatiquement, aucun appel d'animation manuel nécessaire.
function handleMouseLeave() { mouseX.set(0); mouseY.set(0); }
Quand l'utiliser
Ce composant est idéal quand tu as besoin d'un hero qui communique du soin et de l'interactivité dans les trois premières secondes, lancements de produits SaaS, portfolios d'agences et landing pages d'outils de design. Le layout à carte unique garde le focus serré, donc il fonctionne mieux quand la proposition de valeur tient en un titre et un court paragraphe. Évite-le sur les pages e-commerce où le hero concurrence une image produit, sur les pages centrées sur un formulaire comme les flux d'inscription, et sur toute page dont l'audience principale est uniquement mobile : l'inclinaison ne se déclenche jamais sans souris, donc tu ajouterais de la complexité sans effet visible.
Utilisé par
- Stripe, Utilise une légère inclinaison 3D sur les cartes de fonctionnalités produit pour donner de la profondeur à son site marketing.
- Linear, Applique des maquettes UI inclinées en perspective dans ses heros pour donner un aspect tridimensionnel aux captures d'écran produit.
- Vercel, Utilise la profondeur 3D et des réponses au survol basées sur des springs sur les cartes de présentation de fonctionnalités de sa page d'accueil.
- Framer, Présente des interactions d'inclinaison 3D dans son propre marketing comme démo live de ce que l'outil permet.
FAQ
Pourquoi l'inclinaison ne fonctionne-t-elle pas sur mobile ?
L'effet repose sur des événements mousemove qui ne se déclenchent pas sur écran tactile. La carte s'affiche plate sur mobile et présente quand même tout le contenu. Si tu as besoin d'un équivalent tactile, tu peux brancher les événements deviceorientation ou une API gyroscope, ce qui nécessite une permission explicite sur iOS 13+.
Peut-on augmenter l'angle d'inclinaison au-delà de 8 degrés ?
Oui. Change l'intervalle de sortie dans les appels useTransform de [8, -8] vers ce qui convient à ton design. Au-delà de 15-20 degrés la distorsion devient difficile à lire et la lisibilité du texte chute fortement. Garde la valeur de perspective proportionnelle, une perspective plus petite (ex. 600px) amplifie la distorsion au même angle.
Comment régler le spring pour qu'il soit plus vif ou plus rebondissant ?
Augmente stiffness pour que la carte suive le curseur plus vite ; baisse damping pour ajouter du dépassement et du rebond. Une stiffness de 400 avec damping 20 donne un ressenti vif et rebondissant. Une stiffness de 80 avec damping 25 produit un traînage lent. Les valeurs par défaut (200/30) se situent au milieu et fonctionnent pour la plupart des contextes produit.
transformStyle: preserve-3d a-t-il des problèmes de compatibilité navigateur ?
Il est supporté par tous les navigateurs modernes. Le seul cas limite est Safari sur d'anciennes versions iOS (avant 15), où preserve-3d peut être ignoré dans certains contextes d'empilement. Teste avec overflow:hidden et z-index si tu vois l'effet 3D s'effondrer sur Safari.