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.
"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.
npx shadcn@latest add https://www.gassierconsulting.com/r/texto-proximidadEjemplo de uso de Texto por proximidad
Después de copiar texto-proximidad.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
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.
| Propiedad | Tipo | Descripción |
|---|---|---|
text? | string | Opcional. |
radius? | number | Radio de influencia en píxeles. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
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.
| Variable | Valor por defecto |
|---|---|
--gctpr-fg | #111827 |
--w | 300 |
--wt | 300 |
--p | 0 |
--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".