Ir al contenido
Evaluar mi proyecto
Componentes · Texto y animacionesDisponible

Texto por proximidad

Las letras crecen, se elevan y engordan según lo cerca que pasa el puntero, como un dock tipográfico.

Código completo

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

texto-proximidad.tsx62 líneas
Descargar
"use client";

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

/** Texto que reacciona a la cercanía del puntero: cada letra crece, se eleva y engorda según su distancia. React + TypeScript, sin dependencias. */
export type TextoProximidadProps = {
  text?: string;
  /** Radio de influencia en píxeles. */
  radius?: number;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gctpr-root { display: inline-block; max-width: 100%; color: var(--gctpr-fg, #111827); font: 300 clamp(36px, 8vw, 84px)/1.15 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; letter-spacing: -.02em; cursor: default; touch-action: pan-y; }
.gctpr-root *, .gctpr-root *::before, .gctpr-root *::after { box-sizing: border-box; }
.gctpr-c { display: inline-block; white-space: pre; transform-origin: 50% 80%; will-change: transform; transition: transform .12s linear, color .12s linear, font-variation-settings .12s linear; font-variation-settings: "wght" var(--w, 300); font-weight: var(--wt, 300); transform: translateY(calc(var(--p, 0) * -.14em)) scale(calc(1 + var(--p, 0) * .5)); color: color-mix(in srgb, var(--gctpr-hi, #e4572e) calc(var(--p, 0) * 100%), var(--gctpr-fg, #111827)); }
@media (prefers-reduced-motion: reduce) { .gctpr-c { transition: none; transform: none !important; } }`;

export function TextoProximidad({ text = "Cerca de ti", radius = 140, className = "", style }: TextoProximidadProps) {
  const chars = useRef<(HTMLSpanElement | null)[]>([]);

  function move(event: PointerEvent<HTMLSpanElement>) {
    if (event.pointerType === "touch") return;
    chars.current.forEach((el) => {
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const d = Math.hypot(event.clientX - (rect.left + rect.width / 2), event.clientY - (rect.top + rect.height / 2));
      const p = Math.max(0, 1 - d / radius);
      const eased = p * p * (3 - 2 * p);
      el.style.setProperty("--p", eased.toFixed(3));
      el.style.setProperty("--w", String(Math.round(300 + eased * 600)));
      el.style.setProperty("--wt", String(Math.round((300 + eased * 600) / 100) * 100));
    });
  }
  function leave() {
    chars.current.forEach((el) => {
      el?.style.setProperty("--p", "0");
      el?.style.setProperty("--w", "300");
      el?.style.setProperty("--wt", "300");
    });
  }

  return (
    <span className={`gctpr-root ${className}`} style={style} onPointerMove={move} onPointerLeave={leave} aria-label={text} role="text">
      <style>{CSS}</style>
      {Array.from(text).map((char, index) => (
        <span
          key={index}
          ref={(node) => {
            chars.current[index] = node;
          }}
          className="gctpr-c"
          aria-hidden="true"
        >
          {char}
        </span>
      ))}
    </span>
  );
}

Ú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-proximidad

Ejemplo de uso de Texto por proximidad

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

ejemplo.tsx5 líneas
import { TextoProximidad } from "./texto-proximidad";

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

Propiedades de TextoProximidad

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

PropiedadTipoDescripción
text?stringOpcional.
radius?numberRadio de influencia en píxeles.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Texto por proximidad 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
--gctpr-fg#111827
--w300
--wt300
--p0
--gctpr-hi#e4572e

Accesibilidad y rendimiento

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

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Declara roles ARIA para lectores de pantalla: text.
  • Reacciona al puntero con eventos pointer (ratón, lápiz y táctil).
  • Calcula tonos con color-mix(), así que un solo color base alcanza.
  • 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)