Ir al contenido
Evaluar mi proyecto
Componentes · TarjetasDisponible

Tarjeta holográfica con inclinación

Se inclina en 3D hacia el puntero y su brillo cambia de color con el ángulo.

Pase anual
Nº 0427
Ana Gómez

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

holo-tilt-card.tsx75 líneas
Descargar
"use client";

/**
 * Tarjeta con inclinación 3D que sigue al puntero y una capa holográfica que cambia de color con el ángulo.
 * Solo con ratón: en pantallas táctiles y con movimiento reducido se queda quieta.
 *
 *   <HoloTiltCard style={{ width: 320, height: 200 }}>
 *     <h3>Pase anual</h3>
 *   </HoloTiltCard>
 */

import { useRef, type CSSProperties, type HTMLAttributes, type PointerEvent } from "react";

export type HoloTiltCardProps = HTMLAttributes<HTMLDivElement> & {
  /** Inclinación máxima en grados. */
  maxTilt?: number;
};

const CSS = `
.gch-scene { perspective: 900px; display: inline-block; }
.gch-card { --gch-rx: 0deg; --gch-ry: 0deg; --gch-px: 50%; --gch-py: 50%; --gch-o: .3; position: relative; box-sizing: border-box; min-width: 260px; min-height: 160px; padding: 24px; overflow: hidden; border: 1px solid rgba(255, 255, 255, .16); border-radius: 22px; color: #f4f2ff;
  background: radial-gradient(120% 90% at 0% 0%, #2b2559 0%, transparent 60%), linear-gradient(160deg, #15122e, #0b0a1b);
  box-shadow: 0 30px 60px -20px rgba(8, 5, 40, .7), 0 0 0 1px rgba(0, 0, 0, .4);
  transform: rotateX(var(--gch-rx)) rotateY(var(--gch-ry)); transform-style: preserve-3d; transition: transform .7s cubic-bezier(.16, 1, .3, 1), box-shadow .3s; will-change: transform; font-family: inherit; }
.gch-card[data-live="true"] { transition: transform .08s linear, box-shadow .3s; box-shadow: 0 40px 70px -22px rgba(8, 5, 40, .85), 0 0 0 1px rgba(0, 0, 0, .4); }
.gch-card::before, .gch-card::after { content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit; opacity: var(--gch-o); transition: opacity .35s; }
.gch-card::before { mix-blend-mode: color-dodge;
  background: repeating-linear-gradient(115deg, rgba(255, 70, 190, .55) 0, rgba(70, 230, 255, .55) 9%, rgba(255, 235, 90, .5) 18%, rgba(130, 255, 160, .5) 27%, rgba(255, 70, 190, .55) 36%) calc(var(--gch-px) * -1.4) 0 / 220% 220%; }
.gch-card::after { background: radial-gradient(circle at var(--gch-px) var(--gch-py), rgba(255, 255, 255, .5), transparent 46%); mix-blend-mode: soft-light; }
.gch-card > * { position: relative; transform: translateZ(24px); }
@media (hover: none), (prefers-reduced-motion: reduce) { .gch-card { transform: none !important; transition: none; } .gch-card::before, .gch-card::after { opacity: .35; } }
`;

export function HoloTiltCard({ maxTilt = 14, className, style, onPointerMove, onPointerLeave, children, ...props }: HoloTiltCardProps) {
  const ref = useRef<HTMLDivElement>(null);

  function move(event: PointerEvent<HTMLDivElement>) {
    const card = ref.current;
    if (card && event.pointerType !== "touch") {
      const box = card.getBoundingClientRect();
      const x = (event.clientX - box.left) / box.width;
      const y = (event.clientY - box.top) / box.height;
      card.dataset.live = "true";
      card.style.setProperty("--gch-ry", `${((x - 0.5) * 2 * maxTilt).toFixed(2)}deg`);
      card.style.setProperty("--gch-rx", `${((0.5 - y) * 2 * maxTilt).toFixed(2)}deg`);
      card.style.setProperty("--gch-px", `${(x * 100).toFixed(1)}%`);
      card.style.setProperty("--gch-py", `${(y * 100).toFixed(1)}%`);
      card.style.setProperty("--gch-o", "1");
    }
    onPointerMove?.(event);
  }

  function leave(event: PointerEvent<HTMLDivElement>) {
    const card = ref.current;
    if (card) {
      card.dataset.live = "false";
      ["--gch-rx", "--gch-ry", "--gch-o"].forEach((name) => card.style.removeProperty(name));
      card.style.setProperty("--gch-px", "50%");
      card.style.setProperty("--gch-py", "50%");
    }
    onPointerLeave?.(event);
  }

  return (
    <>
      <style>{CSS}</style>
      <div className="gch-scene">
        <div ref={ref} {...props} className={["gch-card", className].filter(Boolean).join(" ")} style={style as CSSProperties} onPointerMove={move} onPointerLeave={leave}>
          {children}
        </div>
      </div>
    </>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/tarjeta-holografica

Ejemplo de uso de Tarjeta holográfica con inclinación

Después de copiar holo-tilt-card.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { HoloTiltCard } from "./holo-tilt-card";

export default function Ejemplo() {
  return <HoloTiltCard />;
}

Accesibilidad y rendimiento

El archivo tiene 75 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Tarjeta holográfica con inclinación por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Reacciona al puntero con eventos pointer (ratón, lápiz y táctil).
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React · Next.js (quita la directiva use client fuera de Next.js) · Sin Tailwind

Acerca del producto

Una tarjeta que se inclina en perspectiva siguiendo al puntero, con una capa holográfica de arcoíris que se desplaza con el ángulo y un reflejo de luz. El contenido flota delante de la tarjeta. Con pantallas táctiles o movimiento reducido se queda quieta.

Incluye

Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.

  • Inclinación máxima configurable (maxTilt)
  • Capa holográfica con color-dodge y reflejo suave
  • Contenido con profundidad (translateZ)
  • Sin dependencias ni librerías de animación

Cómo usarlo

Dale tamaño con style o className y pon tu contenido dentro. Funciona mejor sobre fondos oscuros.