Créer un carrousel avant/après en React
Un carrousel avant/après React associe chaque item à un état booléen (showAfter) et enveloppe l'image affichée dans un bloc AnimatePresence pour que Framer Motion enchaîne les deux états en fondu. Des boutons fléchés et des indicateurs à points gèrent la navigation ; chaque changement de paire réinitialise showAfter à false, donc toute nouvelle paire commence toujours sur la vue avant.
- Stack : React 18 + Framer Motion 11 + Lucide React, ~140 lignes, zéro dépendance supplémentaire.
- API Framer Motion clé : AnimatePresence mode='wait', motion.div avec opacité 0→1.
- Prend en charge les URLs d'images (background-image) et des couleurs pleines comme fallback par paire.
- Accessible : chaque bouton de navigation porte un aria-label ; les boutons-points nomment leur paire cible.
- Responsive par défaut (max-width 800px, aspect-ratio 3/2, taille de texte fluide via clamp).
Before After Carousel est une section React qui permet de parcourir un ensemble de paires de transformation, une à la fois, puis de basculer entre l'état avant et après d'un simple bouton. Elle s'intègre partout où l'on veut présenter des résultats séquentiels : révisions de design, projets de rénovation, progressions cosmétiques. Le layout est minimaliste pour que le contenu reste au premier plan.
Anatomie
La section comporte un en-tête centré (titre + description) au-dessus d'un canvas limité à 800px. Dans ce canvas : une zone d'image avec un aspect-ratio fixe 3:2 et des coins arrondis, un badge d'état épinglé en haut à gauche indiquant 'Avant' ou 'Après', une rangée de contrôles en dessous (flèches précédent/suivant, compteur de paire, label, bouton bascule) et une bande d'indicateurs à points en bas. Le point actif s'étire de 8px à 24px de large pour signaler la position courante sans se reposer sur la couleur seule.
Comment ça marche
La transition entre avant et après est pilotée par AnimatePresence mode='wait' qui enveloppe une motion.div dont la clé est `${pair.id}-${showAfter ? 'after' : 'before'}`. Quand la clé change, la frame sortante s'efface d'abord (opacité 0, durée 0,4s) puis la frame entrante apparaît en fondu. Un tableau d'easing personnalisé [0.16, 1, 0.3, 1] donne aux deux fondus un départ rapide et une fin douce. La navigation vers une nouvelle paire appelle setShowAfter(false) de manière synchrone pour que le changement de clé et le changement d'état produisent un seul crossfade propre plutôt que deux consécutifs.
Comment le coder en React
Modéliser chaque paire avec une interface typée
Définis une interface Pair avec un id, un label et des URLs optionnelles beforeImage/afterImage accompagnées de fallbacks beforeColor/afterColor. Passe le tableau en prop pour que le composant reste purement présentationnel.
interface Pair { id: string; label: string; beforeImage?: string; afterImage?: string; beforeColor?: string; afterColor?: string; }Calculer le fond à partir de l'état courant
Dans la motion.div, calcule le fond au rendu : si showAfter est vrai et afterImage est défini, utilise un background-image CSS url() ; sinon replie sur afterColor, puis sur le token accent. La même logique s'applique côté before avec beforeImage et beforeColor.
background: showAfter ? (pair.afterImage ? `url(${pair.afterImage}) center/cover` : pair.afterColor ?? "var(--color-accent)") : (pair.beforeImage ? `url(${pair.beforeImage}) center/cover` : pair.beforeColor ?? "var(--color-background-alt)"),Clé AnimatePresence pour des crossfades propres
Enveloppe la motion.div dans AnimatePresence mode='wait'. Utilise une clé composée qui combine l'id de la paire et la vue courante, pour que tout changement d'état, navigation ou bascule, produise un nouveau cycle mount/unmount plutôt qu'une animation interrompue.
<AnimatePresence mode="wait"> <motion.div key={`${pair.id}-${showAfter ? "after" : "before"}`} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }} /> </AnimatePresence>Réinitialiser l'état à la navigation
Dans les gestionnaires prev et next, appelle setShowAfter(false) en même temps que la mise à jour de l'index. Cela garantit que la nouvelle paire s'ouvre toujours sur sa vue before, rendant le pattern de révélation intentionnel plutôt qu'aléatoire.
const prev = () => { setCurrent((c) => (c - 1 + pairs.length) % pairs.length); setShowAfter(false); };
Quand l'utiliser
Ce carrousel convient aux landing pages où tu as deux cas de transformation ou plus à montrer, soins beauté, travaux de rénovation, avant/après design, portfolios de retouche photo. Il fonctionne mieux avec quatre à huit paires ; moins de paires rend le carrousel creux, et au-delà de huit la bande de points devient ingérable. À éviter si tu as besoin des deux états visibles simultanément (préfère alors une vue partagée avec curseur), ou si toutes tes paires sont textuelles et n'ont rien de visuel à révéler.
Utilisé par
- Squarespace, Utilise des carrousels avant/après dans sa galerie de templates pour montrer les transformations de design de site.
- Lightroom (Adobe), Présente les résultats de retouche photo avec des bascules avant/après sur les pages marketing et d'onboarding.
- Houzz, Affiche des paires de rénovation domiciliaire en navigation avant/après séquentielle dans ses galeries de projets.
FAQ
Pourquoi AnimatePresence mode='wait' plutôt qu'une transition CSS opacity ?
mode='wait' garantit que l'élément sortant disparaît complètement avant que l'élément entrant n'apparaisse, produisant un crossfade séquentiel propre. Une transition CSS classique sur un rendu conditionnel superposerait visuellement les deux états pendant la transition.
Puis-je utiliser de vraies images plutôt que des couleurs de remplacement ?
Oui. Passe beforeImage et afterImage comme URLs absolues ou relatives dans chaque objet Pair. Le composant les affiche en CSS background-image avec un redimensionnement center/cover. Sans URL, il se replie sur beforeColor ou afterColor, puis sur les tokens du thème.
Comment modifier le nombre d'items visibles ou le comportement en boucle ?
Le composant affiche une paire à la fois et boucle en modulo aux deux extrémités. Pour désactiver la boucle, plafonne l'index : `setCurrent(c => Math.max(0, c - 1))` pour prev et `Math.min(pairs.length - 1, c + 1)` pour next, puis masque les flèches aux limites.
Le composant est-il accessible sans souris ?
Tous les contrôles interactifs sont des boutons natifs avec des attributs aria-label, donc la navigation clavier fonctionne sans configuration. Les indicateurs à points nomment chacun leur paire cible. Le badge d'état est du texte visible, pas une affordance icône seule.