Texto que se descifra
Las letras se mezclan y se fijan de izquierda a derecha.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
/**
* Texto que se descifra: las letras cambian al azar y se fijan de izquierda a derecha. Se activa al montarse, al entrar en pantalla o al pasar el puntero.
* Usa una fuente monoespaciada para que el ancho no cambie mientras se mezcla. Con movimiento reducido muestra el texto final.
*
* <ScrambleText text="Acceso concedido" trigger="hover" />
*/
import { useCallback, useEffect, useRef, useState, type CSSProperties } from "react";
export type ScrambleTextProps = {
text: string;
/** Cuándo se descifra: al montar, al entrar en pantalla o al pasar el puntero (también al montar). */
trigger?: "mount" | "view" | "hover";
/** Duración en milisegundos. */
duration?: number;
/** Caracteres que se usan para mezclar. */
charset?: string;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcsc-root { display: inline-block; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-variant-ligatures: none; white-space: pre-wrap; }
.gcsc-mixed { opacity: .55; }
`;
export function ScrambleText({ text, trigger = "mount", duration = 1100, charset = "!<>-_\\/[]{}—=+*^?#01", className, style }: ScrambleTextProps) {
const [frame, setFrame] = useState({ solved: text.length, noise: "" });
const rootRef = useRef<HTMLSpanElement>(null);
const raf = useRef(0);
const run = useCallback(() => {
cancelAnimationFrame(raf.current);
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setFrame({ solved: text.length, noise: "" }); return; }
const start = performance.now();
const tick = (now: number) => {
const progress = Math.min(1, (now - start) / duration);
const solved = Math.floor(progress * text.length);
const noise = Array.from(text.slice(solved), (char) => (char === " " ? " " : charset[Math.floor(Math.random() * charset.length)])).join("");
setFrame({ solved, noise });
if (progress < 1) raf.current = requestAnimationFrame(tick);
else setFrame({ solved: text.length, noise: "" });
};
raf.current = requestAnimationFrame(tick);
}, [text, duration, charset]);
useEffect(() => {
const element = rootRef.current;
if (trigger === "view" && element) {
const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { observer.disconnect(); run(); } }, { threshold: 0.5 });
observer.observe(element);
return () => { observer.disconnect(); cancelAnimationFrame(raf.current); };
}
const start = requestAnimationFrame(run);
return () => { cancelAnimationFrame(start); cancelAnimationFrame(raf.current); };
}, [trigger, run]);
return (
<>
<style>{CSS}</style>
<span ref={rootRef} className={["gcsc-root", className].filter(Boolean).join(" ")} style={style} aria-label={text} onPointerEnter={trigger === "hover" ? run : undefined}>
<span aria-hidden="true">
{text.slice(0, frame.solved)}
<span className="gcsc-mixed">{frame.noise}</span>
</span>
</span>
</>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/texto-descifradoEjemplo de uso de Texto que se descifra
Después de copiar scramble-text.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { ScrambleText } from "./scramble-text";
export default function Ejemplo() {
return <ScrambleText trigger="mount" text="Ejemplo" />;
}Propiedades de ScrambleText
ScrambleText acepta 6 propiedades; 1 es obligatoria.
| Propiedad | Tipo | Descripción |
|---|---|---|
text | string | Obligatoria. |
trigger? | "mount" | "view" | "hover" | Cuándo se descifra: al montar, al entrar en pantalla o al pasar el puntero (también al montar). |
duration? | number | Duración en milisegundos. |
charset? | string | Caracteres que se usan para mezclar. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Variantes disponibles
trigger:mount,view,hover
Accesibilidad y rendimiento
El archivo tiene 72 líneas (3 KB) y no necesita @keyframes. Esto es lo que hace Texto que se descifra por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Expone su estado a tecnologías de apoyo con atributos aria-*.
- Se pausa cuando sale de la pantalla para no gastar batería ni CPU.
- Anima con requestAnimationFrame, sincronizado con el refresco de pantalla.
- Es un componente de cliente de Next.js: incluye la directiva "use client".
Acerca del producto
Un texto cuyas letras cambian al azar y se van fijando de izquierda a derecha hasta formar la frase. Se activa al montarse, al entrar en pantalla o al pasar el puntero. Usa una fuente monoespaciada para que el ancho no cambie.
Incluye
Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.
- Tres disparadores: mount, view y hover
- Duración y caracteres de mezcla configurables
- El texto real siempre está en aria-label
- Con movimiento reducido muestra el texto final
Cómo usarlo
Pasa text y elige trigger. Tamaño y color por style o className; la fuente monoespaciada se cambia desde CSS (.gcsc-root).