Cómo crear una sección hero con inclinación 3D en React
Un hero React con inclinación 3D mapea la posición del puntero dentro de la tarjeta hacia motion values rotateX y rotateY, las pasa por springs de Framer Motion (stiffness 200, damping 30) y las aplica a un motion.div con transformStyle:'preserve-3d' y una perspective del padre de 1200px. La tarjeta se inclina hasta 8 grados en cada eje y luego vuelve a su posición plana cuando el cursor sale.
- Stack: React 18 + Framer Motion 11 + Tailwind v4 + lucide-react, ~175 líneas.
- API principal: useMotionValue, useSpring, useTransform, sin dependencias aparte de framer-motion.
- El rango de inclinación es de ±8 grados en ambos ejes; los ajustes del spring pueden adaptarse según la sensación de marca.
- Accesible: el contenido es HTML estándar sin capas ocultas; la inclinación es decorativa y no altera el orden de tabulación.
- En dispositivos táctiles la tarjeta permanece plana, la inclinación no se activa sin un puntero de ratón.
Hero 3D Tilt es un hero centrado construido en torno a una única tarjeta flotante que gira en tres dimensiones a medida que el cursor se desplaza sobre ella. La inclinación sigue al puntero con física de spring, no un mapeo lineal rígido, por lo que la respuesta se siente con peso y natural. Funciona para productos SaaS y sitios de agencias que buscan una primera impresión táctil sin recurrir a vídeo a pantalla completa ni a WebGL complejo.
Anatomía
La sección exterior aplica una perspective CSS de 1200px sobre el viewport, dando profundidad a la escena 3D. Dentro hay un contenedor centrado y, dentro de este, un único motion.div (la tarjeta) con transformStyle:'preserve-3d', que es donde se aplican rotateX y rotateY. La tarjeta contiene tres capas visuales apiladas mediante z-index: un panel de fondo de cristal (borde + relleno background-alt), un degradado radial difuminado a modo de halo de acento en la parte superior, y el contenido real (badge opcional, h1 con una palabra de acento en cursiva, descripción y un botón CTA en forma de píldora). Todos los colores hacen referencia a tokens CSS custom properties, por lo que la tarjeta se adapta a cualquier preset de tema.
Cómo funciona
La técnica encadena tres hooks de Framer Motion. useMotionValue mantiene mouseX y mouseY como floats normalizados en el intervalo [-0.5, 0.5], derivados de la posición del cursor respecto al bounding box de la tarjeta. useTransform convierte cada valor a grados: mouseY se mapea a rotateX (la parte frontal se inclina hacia arriba cuando el cursor está cerca de la parte superior) y mouseX se mapea a rotateY (el borde izquierdo se eleva cuando el cursor se mueve a la derecha). Ambos valores derivados pasan luego por useSpring con stiffness 200 y damping 30, añadiendo inercia para que la tarjeta se estabilice en lugar de saltar de golpe. En mouseleave, los dos motion values vuelven a 0, y el spring devuelve la tarjeta suavemente a su posición plana de reposo.
Cómo crearlo en React
Define la perspective en el padre
La perspective CSS debe estar en un elemento padre, no en el elemento que rota. Añade perspective:'1200px' como estilo inline en la sección exterior. Un valor entre 800px y 1500px da una profundidad convincente sin distorsionar en exceso la tarjeta en la inclinación máxima.
<section style={{ perspective: "1200px" }}> <motion.div style={{ rotateX, rotateY, transformStyle: "preserve-3d" }}> {/* card content */} </motion.div> </section>Sigue el puntero en coordenadas normalizadas
En cada evento mousemove, lee getBoundingClientRect desde el ref de la tarjeta y luego divide el offset del cursor entre el ancho y el alto de la tarjeta. Restar 0.5 centra el intervalo en cero, así que la tarjeta queda plana cuando el cursor está en el centro y se inclina en los bordes.
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); }Mapea las coordenadas a una rotación amortiguada por spring
Pasa mouseX y mouseY por useTransform para escalarlos a un rango de grados, y luego envuelve cada resultado en useSpring. La stiffness controla la rapidez con que la tarjeta persigue el puntero; el damping controla cuánto se pasa antes de estabilizarse. Una stiffness de 200 con damping 30 resulta reactiva sin ser brusca.
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, });Reinicia al salir el ratón
Vuelve a poner mouseX y mouseY a 0 en el handler onMouseLeave. Como los valores derivados rotateX/rotateY están envueltos en springs, la tarjeta regresa a plano automáticamente, sin necesidad de ninguna llamada de animación manual.
function handleMouseLeave() { mouseX.set(0); mouseY.set(0); }
Cuándo usarlo
Recurre a este componente cuando necesites un hero que comunique cuidado e interactividad en los primeros tres segundos: lanzamientos de productos SaaS, portfolios de agencias y landing pages de herramientas de diseño son el encaje natural. El layout de tarjeta única mantiene el foco cerrado, por lo que funciona mejor cuando la propuesta de valor cabe en un titular y un párrafo corto. Evítalo en páginas de producto e-commerce donde el hero compite con una imagen de producto, en páginas centradas en formularios como los flujos de registro, y en cualquier página cuya audiencia principal sea exclusivamente móvil: la inclinación nunca se activa sin ratón, así que estarías añadiendo complejidad sin ningún efecto.
Usado por
- Stripe, Usa una sutil inclinación 3D en las tarjetas de funcionalidades de producto para dar profundidad a su sitio de marketing.
- Linear, Aplica maquetas de UI inclinadas en perspectiva en sus secciones hero para que las capturas de pantalla del producto se sientan tridimensionales.
- Vercel, Emplea profundidad 3D y respuestas al hover basadas en springs en las tarjetas de presentación de funcionalidades a lo largo de su página de inicio.
- Framer, Muestra interacciones de inclinación 3D directamente en su propio marketing como demo en vivo de lo que permite la herramienta.
FAQ
¿Por qué la inclinación no funciona en móvil?
El efecto se basa en eventos mousemove, que no se disparan en pantallas táctiles. La tarjeta se renderiza plana en móvil y aun así muestra todo el contenido. Si necesitas un equivalente táctil, puedes conectar los eventos deviceorientation o una API de giroscopio, aunque eso requiere permiso explícito en iOS 13+.
¿Puedo aumentar el ángulo de inclinación más allá de 8 grados?
Sí. Cambia el rango de salida en las llamadas a useTransform de [8, -8] al valor que encaje con tu diseño. Más allá de 15-20 grados la distorsión se vuelve difícil de leer y la legibilidad del texto cae bruscamente. Mantén el valor de perspective proporcional, una perspective más pequeña (por ejemplo 600px) amplifica la distorsión con el mismo ángulo.
¿Cómo ajusto la sensación del spring para que sea más vivo o más rebotante?
Sube stiffness para que la tarjeta persiga el cursor más rápido; baja damping para añadir sobreimpulso y rebote. Una stiffness de 400 con damping 20 da una sensación viva y rebotante. Una stiffness de 80 con damping 25 produce un arrastre lento y perezoso. Los valores por defecto (200/30) quedan en el medio y funcionan para la mayoría de contextos de producto.
¿transformStyle: preserve-3d tiene problemas de compatibilidad con los navegadores?
Está soportado en todos los navegadores modernos. El único caso límite es Safari en versiones antiguas de iOS (anteriores a la 15), donde preserve-3d puede ignorarse dentro de ciertos contextos de apilamiento. Prueba con overflow:hidden y z-index si ves que el efecto 3D se colapsa en Safari.