Retour au catalogue

Hero Perspective Zoom

Hero cinématique avec entrée 3D : le contenu démarre incliné (rotateX 15°, scale 0.9) et se redresse progressivement au scroll. Inclut un mockup dashboard avec son propre effet de tilt.

herocomplex Both Responsive a11y
boldelegantcorporatesaasagencyportfoliocentered
Theme

Cómo crear un hero con tilt en perspectiva controlado por el scroll en React

Un hero con perspectiva controlada por el scroll en React aplica un contenedor CSS perspective y usa useScroll y useTransform de Framer Motion para convertir el progreso del scroll en valores rotateX y scale, que luego suaviza con useSpring para que la inclinación se resuelva de forma natural a medida que el visitante baja.

  • Stack: React + Framer Motion + Lucide React, ~480 líneas, cero dependencias runtime adicionales.
  • API principal de Framer Motion: useScroll, useTransform, useSpring, transformStyle: preserve-3d.
  • El mockup del dashboard tiene su propio tilt independiente (rotateX de 22° a 2°), algo más marcado que la capa de texto.
  • Accesible: HTML semántico, aria-hidden en los divs decorativos, contenido legible en todas las posiciones de scroll.
  • El efecto 3D se ve por completo en desktop; en móvil se aplican las mismas transformaciones, pero el escorzo de la perspectiva resulta menos marcado en viewports pequeños.

Este hero se abre inclinado hacia el visitante, con el bloque de texto a 15° en el eje X y el mockup del dashboard a 22°, y luego se despliega en un layout plano a medida que el usuario hace scroll. El resultado se percibe como un retroceso de cámara cinematográfico, el tipo de secuencia de entrada que sugiere un producto premium sin recurrir a un vídeo de fondo.

Anatomía

La sección tiene dos capas decorativas (una fina línea de acento en el borde superior y un halo ambiental difuminado bajo el mockup) y luego un único wrapper perspective fijado en 1200px. Dentro de ese wrapper, una motion.div lleva el bloque de texto y una motion.div anidada lleva el mockup, cada una con sus propios springs de rotateX y scale. El bloque de texto se centra a 700px de max-width; el mockup se sitúa debajo a 900px de max-width con una relación 16:9, mostrando una barra de chrome y un dashboard falso a dos columnas (nav lateral + tarjetas de estadísticas + gráfico de barras).

Cómo funciona

El hook de scroll se engancha al elemento section mediante useRef y lee scrollYProgress entre los offsets ["start start", "end start"]. Dos transformaciones brutas convierten ese progreso en los valores de tilt: rotateX pasa de 15° a 0° durante la primera mitad del recorrido de scroll, y scale de 0.9 a 1.0 en el mismo rango. La opacity sube de 0.7 a 1.0 durante el primer cuarto. Cada valor bruto pasa por un useSpring (stiffness 60, damping 20) para que la animación se retrase un poco y resulte física. El mockup usa springs separados con un ángulo de inicio mayor (22° a 2°) y una scale de inicio menor (0.88), creando una profundidad tipo parallax entre las dos capas pese a compartir la misma fuente de scroll.

Cómo crearlo en React

  1. Configurar la fuente de scroll y las transformaciones brutas

    Asocia un ref a la section y luego llama a useScroll con ese target y el par de offsets. Pasa scrollYProgress a las llamadas de useTransform para derivar los rangos de rotateX, scale y opacity. Mantén el rango de entrada estrecho (0 a 0.5) para que el efecto se resuelva antes de que la section salga del viewport.

    const sectionRef = useRef<HTMLElement>(null);
    const { scrollYProgress } = useScroll({
      target: sectionRef,
      offset: ["start start", "end start"],
    });
    const rawRotateX = useTransform(scrollYProgress, [0, 0.5], [15, 0]);
    const rawScale   = useTransform(scrollYProgress, [0, 0.5], [0.9, 1]);
    const rawOpacity = useTransform(scrollYProgress, [0, 0.25], [0.7, 1]);
  2. Suavizar los valores brutos con springs

    Envuelve cada motion value bruto en useSpring con una config coherente. Una stiffness baja (60) y un damping moderado (20) producen el efecto de arrastre sin oscilación. Es el spring lo que distingue un tilt cinematográfico de una transición CSS rígida.

    const SPRING = { stiffness: 60, damping: 20, mass: 1 };
    const rotateX = useSpring(rawRotateX, SPRING);
    const scale   = useSpring(rawScale,   SPRING);
    const opacity = useSpring(rawOpacity, SPRING);
  3. Aplicar el contenedor perspective y la capa motion

    Envuelve todo en un div normal con `perspective: 1200px` y `perspectiveOrigin: "50% 30%"`. La motion.div interior recibe los valores del spring y debe tener `transformStyle: "preserve-3d"` y `transformOrigin: "50% 0%"` para que el tilt gire desde el borde superior, no desde el centro.

    <div style={{ perspective: "1200px", perspectiveOrigin: "50% 30%" }}>
      <motion.div
        style={{ rotateX, scale, opacity,
                 transformStyle: "preserve-3d",
                 transformOrigin: "50% 0%" }}
      >
        {/* text + mockup */}
      </motion.div>
    </div>
  4. Dar al mockup su propio tilt más pronunciado

    Anida una segunda motion.div para el mockup con su propia pareja useTransform + useSpring. Arráncala a 22° en lugar de 15° y devuélvela solo hasta 2° (no a 0°) para conservar una profundidad sutil cuando el hero queda completamente desplegado. Esa diferencia entre las dos capas se percibe como parallax.

    const rawMockupRotateX = useTransform(scrollYProgress, [0, 0.5], [22, 2]);
    const rawMockupScale   = useTransform(scrollYProgress, [0, 0.5], [0.88, 1]);
    const mockupRotateX = useSpring(rawMockupRotateX, SPRING);
    const mockupScale   = useSpring(rawMockupScale,   SPRING);

Cuándo usarlo

Este patrón funciona mejor en una landing de SaaS o de producto donde tienes una captura de UI real o un mockup que mostrar. La inclinación atrae la atención hacia la imagen del producto y enmarca la página como una revelación. Evítalo si la sección above-the-fold ya carga animaciones pesadas, o si la acción de conversión principal debe verse de inmediato sin distracciones de movimiento. Como el efecto se dispara con el scroll, también tiene menos sentido en páginas cortas donde el hero ocupa la mayor parte de la altura del viewport.

Usado por

  • Linear, Usa una perspectiva 3D controlada por el scroll en su hero de producto para hacer aparecer el mockup del dashboard desde un ángulo inclinado.
  • Vercel, Aplica transformaciones de scale y profundidad a las capturas de producto en las secciones hero, creando la sensación de que la interfaz emerge hacia el visitante.
  • Loom, Usa una entrada en perspectiva cinematográfica para el mockup de su app en la homepage de marketing, pasando de un ángulo 3D escorzado a una vista plana.

FAQ

¿Por qué useSpring en lugar de aplicar useTransform directamente a la motion.div?

useTransform mapea el progreso del scroll de forma lineal, así que el tilt sigue exactamente la velocidad de scroll y resulta mecánico. useSpring añade masa y damping para que el tilt vaya ligeramente por detrás de la posición de scroll y luego la alcance; ese retraso es lo que se percibe como peso físico y no como una animación CSS.

¿Se rompe el efecto si el usuario tiene activada la preferencia reduced-motion?

El componente no comprueba prefers-reduced-motion por ahora. Para respetarlo, lee la media query con useReducedMotion de Framer Motion e inicializa rotateX y scale en sus valores finales (0° y 1.0) para que no haya ninguna transformación.

¿Puedo reemplazar el mockup integrado por una captura de pantalla real?

Sí. El mockup vive en su propia motion.div y tiene una relación de aspecto 16:9 fija. Sustituye el contenido interior por un <img> o un <Image> de Next.js; el spring de perspectiva que lo envuelve sigue siendo el mismo. Mantén overflow:hidden y border-radius en el contenedor exterior para que la imagen se recorte correctamente.

¿Cómo ajusto el tilt para que se sienta más pesado o más ágil?

Ajusta la config SPRING: una stiffness más baja (p. ej. 30) y una masa más alta lo vuelven lento y cinematográfico; una stiffness más alta (p. ej. 120) con menos damping lo hace más ágil. Los valores de inicio de rotateX (15° y 22°) controlan lo dramático que parece el ángulo inicial; súbelos a 25°/35° para una entrada más exagerada.

"use client";

import { useRef } from "react";
import { motion, useScroll, useTransform, useSpring } from "framer-motion";
import { ArrowRight } from "lucide-react";

interface HeroPerspectiveZoomProps {
  badge?: string;
  title?: string;
  titleAccent?: string;
  description?: string;
  ctaLabel?: string;
  ctaUrl?: string;
  ctaSecondaryLabel?: string;
  ctaSecondaryUrl?: string;
  mockupLabel?: string;
}

const EASE = [0.16, 1, 0.3, 1] as const;
const SPRING = { stiffness: 60, damping: 20, mass: 1 };

export default function HeroPerspectiveZoom({

Code complet réservé à Pro

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

Passer en Pro, 9,99€/mois

Reseñas

Hero 3D React al scroll (tilt perspective), código +