Ir al contenido
Evaluar mi proyecto
Componentes · Texto y animacionesDisponible

Texto con perspectiva interactiva

Titular que cambia de perspectiva con el puntero y proyecta un reflejo tipográfico; estático en táctil y movimiento reducido.

Otra perspectiva.

Código completo

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

texto-perspectiva.tsx68 líneas
Descargar
"use client";

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

/** Texto con perspectiva interactiva. React + TypeScript, sin dependencias. Personaliza con variables --gcxe-*. */
export type TextoPerspectivaProps = {
  text?: string;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcxe-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcxe-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcxe-root *, .gcxe-root *::before, .gcxe-root *::after { box-sizing: border-box; }
.gcxe-root button, .gcxe-root input, .gcxe-root textarea, .gcxe-root select { font: inherit; }
.gcxe-root button, .gcxe-root a { -webkit-tap-highlight-color: transparent; }
.gcxe-root :focus-visible { outline: 2px solid var(--gcxe-accent, #25724b); outline-offset: 4px; }
.gcxe-root ::selection { background: var(--gcxe-accent, #25724b); color: #fff; }
.gcxe-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcxe-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcxe-root *, .gcxe-root *::before, .gcxe-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcxe-root { position:relative; perspective:800px; width:560px; padding:45px 20px 70px; text-align:center; }
.gcxe-root span { display:block; font-size:clamp(34px,7vw,64px); line-height:1.05; font-weight:650; letter-spacing:-.035em; color:var(--gcxe-accent,#273830); transform:rotateX(var(--gcxe-y,0deg)) rotateY(var(--gcxe-x,0deg)); transition:transform .35s cubic-bezier(.16,1,.3,1); }
.gcxe-shadow { position:absolute; top:calc(50% + 28px); left:20px; right:20px; font-size:clamp(34px,7vw,64px); line-height:1.05; font-weight:650; letter-spacing:-.035em; color:#25382d; opacity:.1; transform:rotateX(65deg) scaleY(-.8); filter:blur(2px); pointer-events:none; }
@media(prefers-reduced-motion:reduce) { .gcxe-root span { transform:none; } }`;

export function TextoPerspectiva({
  text = "Otra perspectiva.",
  className = "",
  style,
}: TextoPerspectivaProps) {
  const ref = useRef<HTMLDivElement>(null);
  function move(event: PointerEvent<HTMLDivElement>) {
    if (
      event.pointerType !== "mouse" ||
      window.matchMedia("(prefers-reduced-motion: reduce)").matches
    )
      return;
    const box = event.currentTarget.getBoundingClientRect();
    ref.current?.style.setProperty(
      "--gcxe-x",
      `${((event.clientX - box.left) / box.width) * 12 - 6}deg`,
    );
    ref.current?.style.setProperty(
      "--gcxe-y",
      `${6 - ((event.clientY - box.top) / box.height) * 12}deg`,
    );
  }
  return (
    <div
      ref={ref}
      className={`gcxe-root ${className}`}
      style={style}
      onPointerMove={move}
      onPointerLeave={() => {
        ref.current?.style.setProperty("--gcxe-x", "0deg");
        ref.current?.style.setProperty("--gcxe-y", "0deg");
      }}
    >
      <style>{CSS}</style>
      <span>{text}</span>
      <div className="gcxe-shadow" aria-hidden="true">
        {text}
      </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/texto-perspectiva

Ejemplo de uso de Texto con perspectiva interactiva

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

ejemplo.tsx5 líneas
import { TextoPerspectiva } from "./texto-perspectiva";

export default function Ejemplo() {
  return <TextoPerspectiva text="Ejemplo" />;
}

Propiedades de TextoPerspectiva

TextoPerspectiva acepta 3 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
text?stringOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Texto con perspectiva interactiva con variables CSS

Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.

VariableValor por defecto
--gcxe-fg#202421
--gcxe-accent#25724b
--gcxe-y0deg
--gcxe-x0deg

Accesibilidad y rendimiento

El archivo tiene 68 líneas (3 KB) y no necesita @keyframes. Esto es lo que hace Texto con perspectiva interactiva por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Expone su estado a tecnologías de apoyo con atributos aria-*.
  • 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 18 y 19 · Next.js (componente de cliente)