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

Cómo crear un slider de comparación antes/después arrastrable en React

Un slider antes/después en React coloca dos paneles dentro del mismo contenedor: el panel 'después' se recorta con clip-path CSS `inset(0 0 0 X%)`, donde X es la posición del arrastre. Los eventos de ratón y táctiles actualizan ese porcentaje en el estado, moviendo a la vez el borde de recorte y un tirador visible.

  • Stack: React 18 + Framer Motion 11 + Lucide React, ~270 líneas, sin dependencias adicionales.
  • Mecanismo de arrastre: eventos nativos de ratón/táctil sobre un contenedor con ref, sin necesidad de la API drag de Framer Motion.
  • El panel 'después' usa clip-path inset() de CSS para revelar solo la parte derecha; el panel izquierdo siempre está visible.
  • Cuadrícula de estadísticas opcional bajo el slider, escalonada con Framer Motion al entrar en el viewport mediante useInView.
  • Accesible: el contenido de ambos paneles es texto legible, no imágenes, los lectores de pantalla reciben los dos lados.

BeforeAfterWebsiteRedesign es una sección de comparación por arrastre que permite a los visitantes desplazarse entre un estado 'antiguo' y uno 'nuevo' de una interfaz web. Un tirador central divide la zona: arrástralo a la izquierda para mostrar más del rediseño, empújalo a la derecha para revelar el original. Bajo el slider, unas tarjetas de métricas opcionales refuerzan el mensaje con cifras concretas.

Anatomía

El componente se organiza en tres zonas apiladas. Arriba, un bloque de cabecera centrado con una insignia de proyecto (icono Layout + nombre del proyecto), un titular y un subtítulo que aparecen al hacer scroll mediante useInView. Debajo se encuentra el contenedor de comparación: dos paneles posicionados en absoluto dentro de un div relativo. El panel 'antes' rellena el contenedor por completo; el panel 'después' lo cubre y se recorta por la izquierda con clip-path. Una barra vertical de acento en la posición del arrastre termina en un tirador circular GripVertical. Cada panel enumera cuatro elementos (cabecera, hero, maquetación, CTA) con iconos Minus o Plus para señalar regresión o mejora. La tercera zona es una cuadrícula de estadísticas responsive opcional que se anima en escalonado.

Cómo funciona

El truco es un único valor de estado React `sliderPosition` (0–100, inicialmente 50). Cuando el usuario presiona sobre el div del tirador, un booleano `isDragging` pasa a true. En cada movimiento de ratón/táctil sobre el contenedor, `handleMove` lee el clientX del evento, resta el borde izquierdo del contenedor obtenido con getBoundingClientRect, divide por el ancho del contenedor y multiplica por 100. Ese porcentaje se escribe directamente en el estado. El panel 'después' lo recibe como `clipPath: inset(0 0 0 ${sliderPosition}%)` mediante un estilo inline, aquí no hace falta ningún spring de Framer Motion, la actualización es instantánea y eso mantiene la sensación física del arrastre. El div del tirador vertical recibe `left: ${sliderPosition}%` más `transform: translateX(-50%)` para mantenerse centrado en el borde.

Cómo crearlo en React

  1. Configurar el contenedor y seguir el estado del arrastre

    Crea un ref en el contenedor de comparación y dos valores de estado: un número para la posición de división y un booleano para el arrastre. Asocia onMouseDown al tirador y onMouseUp/onMouseLeave al contenedor para que el arrastre se detenga cuando el puntero sale.

    const sliderRef = useRef<HTMLDivElement>(null);
    const [sliderPosition, setSliderPosition] = useState(50);
    const [isDragging, setIsDragging] = useState(false);
  2. Convertir las coordenadas del puntero en un porcentaje

    En el manejador de movimiento, obtén los límites del contenedor con getBoundingClientRect, acota el desplazamiento clientX entre 0 y el ancho del contenedor, luego divide para obtener un valor de 0–100. Escríbelo en el estado dentro de un useCallback para que la referencia se mantenga estable entre renders.

    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. Recortar el panel 'después' con clip-path inset

    Ambos paneles van en absoluto con inset:0. El panel 'antes' no necesita recorte. El panel 'después' recibe un clipPath inline que corta todo lo que está a la izquierda de la posición del slider. Sin transición CSS, la actualización es síncrona con el arrastre, así que no hay retraso.

    <div style={{
      position: "absolute",
      inset: 0,
      clipPath: `inset(0 0 0 ${sliderPosition}%)`,
    }}>
      {/* after content */}
    </div>
  4. Añadir soporte táctil

    Los eventos táctiles reflejan exactamente los eventos de ratón. En el contenedor, añade onTouchMove que lea e.touches[0].clientX y lo pase a la misma función handleMove. Añade onTouchEnd que reinicie isDragging. Eso cubre el móvil sin ninguna librería adicional.

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

Cuándo usarlo

Ideal para páginas de portafolio de agencias, casos de estudio y landing pages de producto donde necesitas demostrar una transformación, no solo afirmarla. El slider funciona como sección en mitad de una página larga; no sustituye a un hero. Evítalo cuando los estados 'antes' y 'después' son demasiado parecidos visualmente, el contraste debe ser evidente para justificar la interacción. En pantallas muy pequeñas la altura fija de 400px puede resultar apretada; considera reducirla o cambiar a un sencillo conmutador con pestañas por debajo de un breakpoint.

Usado por

  • Stripe, Utiliza comparaciones en paralelo y por revelación en sus casos de estudio de UI de pago para mostrar las mejoras del panel del comerciante.
  • Figma, Los patrones de revelación antes/después aparecen en las páginas de anuncio de funciones de Figma para contrastar las experiencias antiguas y nuevas del editor.
  • Webflow, Los sliders de comparación aparecen en los casos de estudio de clientes de Webflow para demostrar la mejora de rendimiento y diseño del sitio.
  • Shopify, Las historias de éxito de comerciantes usan visuales antes/después para mostrar transformaciones de tienda, a veces con sliders interactivos.

FAQ

¿Por qué no usar la API drag de Framer Motion para el tirador?

Los eventos nativos de ratón/táctil sobre el contenedor son más sencillos aquí porque el arrastre debe limitarse al movimiento horizontal dentro de un elemento concreto. La API drag de Framer Motion añade sobrecarga y exige configurar restricciones extra; getBoundingClientRect más actualizaciones directas de estado son cinco líneas y manejan los casos límite de forma limpia.

¿Puedo usar imágenes en lugar de texto en cada panel?

Sí. Sustituye las filas de texto por una background-image o una etiqueta img en cada panel. El mecanismo de clip-path es idéntico sea cual sea el contenido del panel. Para las imágenes, asegúrate de que ambos paneles tengan las mismas dimensiones para que el borde visual se alinee perfectamente.

¿Cómo evito que la página haga scroll mientras arrastro en móvil?

Llama a e.preventDefault() dentro del manejador onTouchMove. Tendrás que asociar el listener como un event listener no pasivo mediante addEventListener en un useEffect, porque el onTouchMove sintético de React es pasivo por defecto y no se puede prevenir.

El slider salta al primer clic, ¿cómo lo soluciono?

El salto ocurre cuando el clic inicial no cae sobre el tirador sino en otra parte del contenedor. Para solucionarlo, asocia el listener mousedown a todo el contenedor y llama a handleMove de inmediato en el mousedown antes de poner isDragging. Eso lleva primero el tirador a la posición del clic y luego sigue el arrastre con suavidad.

"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

Reseñas

Slider antes/después en React, comparación arrastrable