Retour au catalogue

Website Redesign

Comparaison avant/apres d'une refonte de site web avec slider interactif draggable.

before-aftercomplex Both Responsive a11y
boldelegantagencyportfoliocentered
Theme

Créer un slider de comparaison avant/après draggable en React

Un slider avant/après en React place deux panneaux dans le même conteneur : le panneau 'après' est découpé avec CSS clip-path `inset(0 0 0 X%)` où X est la position du drag. Les événements souris et tactiles mettent à jour ce pourcentage dans l'état, déplaçant à la fois la frontière de découpe et la poignée visible.

  • Stack : React 18 + Framer Motion 11 + Lucide React, ~270 lignes, pas de dépendance supplémentaire.
  • Mécanisme de drag : événements souris/tactile natifs sur un conteneur ref, pas besoin de l'API drag Framer Motion.
  • Le panneau 'après' utilise clip-path inset() CSS pour ne révéler que la partie droite ; le panneau gauche est toujours visible.
  • Grille de stats optionnelle sous le slider, échelonnée avec Framer Motion à l'entrée dans le viewport via useInView.
  • Accessible : le contenu des deux panneaux est du texte lisible, pas des images, les lecteurs d'écran obtiennent les deux côtés.

BeforeAfterWebsiteRedesign est une section de comparaison par drag qui permet aux visiteurs de scrubber entre un état 'avant' et un état 'après' d'une interface web. Une poignée centrale divise la zone : tirez-la à gauche pour exposer davantage du redesign, poussez-la à droite pour révéler l'original. Sous le slider, des cartes de métriques optionnelles renforcent le discours avec des chiffres concrets.

Anatomie

Le composant s'organise en trois zones empilées. En haut, un bloc d'en-tête centré avec un badge projet (icône Layout + nom du projet), un titre et un sous-titre qui apparaissent au scroll via useInView. En dessous se trouve le conteneur de comparaison : deux panneaux positionnés en absolu dans un div relatif. Le panneau 'avant' remplit le conteneur entièrement ; le panneau 'après' le recouvre et est découpé à gauche avec clip-path. Une barre verticale accent à la position du drag se termine par une poignée circulaire GripVertical. Chaque panneau liste quatre éléments (en-tête, hero, mise en page, CTA) avec des icônes Minus ou Plus pour signaler régression ou amélioration. La troisième zone est une grille de stats responsive optionnelle qui s'anime en stagger.

Comment ça marche

Le mécanisme repose sur une seule valeur d'état React `sliderPosition` (0–100, initialement 50). Quand l'utilisateur appuie sur la poignée, un booléen `isDragging` passe à true. À chaque mouvement souris/tactile sur le conteneur, `handleMove` lit clientX, soustrait le bord gauche du conteneur via getBoundingClientRect, divise par la largeur et multiplie par 100. Ce pourcentage est écrit directement dans l'état. Le panneau 'après' le reçoit en `clipPath: inset(0 0 0 ${sliderPosition}%)` via un style inline, pas de spring Framer Motion ici, la mise à jour est instantanée et ça rend le drag physique. Le div de la poignée verticale reçoit `left: ${sliderPosition}%` plus `transform: translateX(-50%)` pour rester centré sur la frontière.

Comment le coder en React

  1. Configurer le conteneur et suivre l'état du drag

    Crée un ref sur le conteneur de comparaison et deux valeurs d'état : un nombre pour la position de split et un booléen pour le drag. Attache onMouseDown à la poignée et onMouseUp/onMouseLeave au conteneur pour que le drag s'arrête quand le pointeur sort.

    const sliderRef = useRef<HTMLDivElement>(null);
    const [sliderPosition, setSliderPosition] = useState(50);
    const [isDragging, setIsDragging] = useState(false);
  2. Convertir les coordonnées du pointeur en pourcentage

    Dans le gestionnaire de mouvement, récupère les limites du conteneur avec getBoundingClientRect, serre l'offset clientX entre 0 et la largeur du conteneur, puis divise pour obtenir une valeur 0–100. Écris-la dans l'état dans un useCallback pour que la référence reste stable entre les rendus.

    const handleMove = useCallback((clientX: number) => {
      if (!sliderRef.current) return;
      const rect = sliderRef.current.getBoundingClientRect();
      const x = Math.max(0, Math.min(clientX - rect.left, rect.width));
      setSliderPosition((x / rect.width) * 100);
    }, []);
  3. Découper le panneau 'après' avec clip-path inset

    Les deux panneaux sont en absolu avec inset:0. Le panneau 'avant' n'a pas besoin de découpe. Le panneau 'après' reçoit un clipPath inline qui coupe tout ce qui est à gauche de la position du slider. Pas de transition CSS, la mise à jour est synchrone avec le drag, donc pas de lag.

    <div style={{
      position: "absolute",
      inset: 0,
      clipPath: `inset(0 0 0 ${sliderPosition}%)`,
    }}>
      {/* after content */}
    </div>
  4. Ajouter le support tactile

    Les événements tactiles reflètent exactement les événements souris. Sur le conteneur, ajoute onTouchMove qui lit e.touches[0].clientX et le passe à la même fonction handleMove. Ajoute onTouchEnd qui remet isDragging à false. Ça couvre le mobile sans librairie supplémentaire.

    onTouchMove={(e) => handleMove(e.touches[0].clientX)}
    onTouchEnd={() => setIsDragging(false)}

Quand l'utiliser

Idéal pour les pages portfolio d'agences, les études de cas et les landing pages produit où il faut prouver une transformation, pas seulement l'affirmer. Le slider fonctionne comme section en milieu de page longue ; il ne remplace pas un hero. À éviter quand les états 'avant' et 'après' sont trop similaires visuellement, le contraste doit être évident pour justifier l'interaction. Sur très petit écran, la hauteur fixe de 400px peut sembler étriquée ; envisage de la réduire ou de passer à un toggle tabulé sous un breakpoint.

Utilisé par

  • Stripe, Utilise des comparaisons côte à côte et à révélation dans ses études de cas d'UI de paiement pour montrer les améliorations du tableau de bord marchand.
  • Figma, Les motifs de révélation avant/après apparaissent sur les pages d'annonce de fonctionnalités de Figma pour contraster les anciennes et nouvelles expériences de l'éditeur.
  • Webflow, Les sliders de comparaison figurent dans les études de cas clients de Webflow pour démontrer l'amélioration des performances et du design.
  • Shopify, Les success stories de marchands utilisent des visuels avant/après pour montrer les transformations de boutique, parfois avec des sliders interactifs.

FAQ

Pourquoi ne pas utiliser l'API drag de Framer Motion pour la poignée ?

Les événements souris/tactile natifs sur le conteneur sont plus simples ici car le drag doit être contraint au mouvement horizontal dans un élément spécifique. L'API drag de Framer Motion ajoute de la surcharge et nécessite une configuration de contrainte supplémentaire ; getBoundingClientRect plus des mises à jour d'état directes tient en cinq lignes et gère les cas limites proprement.

Peut-on utiliser des images plutôt que du texte dans chaque panneau ?

Oui. Remplace les lignes de texte par une background-image ou une balise img dans chaque panneau. Le mécanisme clip-path est identique quel que soit le contenu. Pour les images, assure-toi que les deux panneaux ont les mêmes dimensions pour que la frontière visuelle s'aligne parfaitement.

Comment empêcher le scroll de page pendant le drag sur mobile ?

Appelle e.preventDefault() dans le gestionnaire onTouchMove. Tu devras attacher l'écouteur comme événement non-passif via addEventListener dans un useEffect, car le onTouchMove synthétique de React est passif par défaut et ne peut pas être préventé.

Le slider saute au premier clic, comment le corriger ?

Le saut se produit quand le clic initial n'est pas sur la poignée mais ailleurs sur le conteneur. Pour corriger, attache l'écouteur mousedown sur l'ensemble du conteneur et appelle handleMove immédiatement au mousedown avant de setter isDragging. Ça positionne d'abord la poignée au point de clic, puis suit le drag en douceur.

"use client";

import { useRef, useState, useCallback } from "react";
import { motion, useInView } from "framer-motion";
import { GripVertical, Layout, TrendingUp, Minus, Plus } from "lucide-react";

interface BeforeAfterElements {
  header: string;
  hero: string;
  layout: string;
  cta: string;
}

interface Stat {
  label: string;
  value: string;
}

interface BeforeAfterWebsiteRedesignProps {
  title?: string;
  subtitle?: string;
  projectName?: string;

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Avis

Slider avant/après React, comparaison draggable