Texto que se revela
Cada palabra sube desde una máscara al entrar en pantalla.
Las ideas simples se vuelven producto cuando alguien las construye con cuidado.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
/**
* Texto que se revela palabra por palabra al entrar en pantalla: cada palabra sube desde una máscara, con un leve desenfoque.
* El texto está visible desde el servidor y solo se oculta cuando el componente ya se montó, así que no se pierde sin JavaScript.
*
* <WordReveal as="h2" text="Software que se adapta a tu operación" />
*/
import { useEffect, useRef, useState, type CSSProperties, type ElementType } from "react";
export type WordRevealProps = {
text: string;
/** Etiqueta HTML que se renderiza. */
as?: ElementType;
/** Milisegundos entre palabras. */
stagger?: number;
/** Si es false, repite la animación cada vez que vuelve a entrar en pantalla. */
once?: boolean;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcrv-line { display: block; }
.gcrv-mask { display: inline-block; overflow: hidden; vertical-align: top; padding-bottom: .12em; margin-bottom: -.12em; }
.gcrv-word { display: inline-block; will-change: transform; }
.gcrv-root[data-armed="true"] .gcrv-word { transform: translateY(108%); opacity: 0; filter: blur(6px); }
.gcrv-root[data-armed="true"][data-shown="true"] .gcrv-word { transform: none; opacity: 1; filter: none; transition: transform .9s cubic-bezier(.16, 1, .3, 1) calc(var(--gcrv-i) * var(--gcrv-stagger, 60ms)), opacity .6s ease-out calc(var(--gcrv-i) * var(--gcrv-stagger, 60ms)), filter .7s ease-out calc(var(--gcrv-i) * var(--gcrv-stagger, 60ms)); }
@media (prefers-reduced-motion: reduce) { .gcrv-root[data-armed="true"] .gcrv-word { transform: none; opacity: 1; filter: none; transition: none !important; } }
`;
export function WordReveal({ text, as: Tag = "p", stagger = 60, once = true, className, style }: WordRevealProps) {
const ref = useRef<HTMLElement>(null);
const [armed, setArmed] = useState(false);
const [shown, setShown] = useState(false);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setShown(true);
if (once) observer.disconnect();
} else if (!once) setShown(false);
}, { threshold: 0.35 });
// Hide only once we can reveal: the first paint and no-JS visitors keep the text.
const frame = requestAnimationFrame(() => { setArmed(true); observer.observe(element); });
return () => { cancelAnimationFrame(frame); observer.disconnect(); };
}, [once]);
const words = text.split(/\s+/).filter(Boolean);
return (
<>
<style>{CSS}</style>
<Tag ref={ref} className={["gcrv-root", className].filter(Boolean).join(" ")} style={{ ["--gcrv-stagger" as string]: `${stagger}ms`, ...style } as CSSProperties} data-armed={armed} data-shown={shown}>
{words.map((word, index) => (
<span key={`${word}-${index}`}>
<span className="gcrv-mask"><span className="gcrv-word" style={{ ["--gcrv-i" as string]: index } as CSSProperties}>{word}</span></span>
{index < words.length - 1 ? " " : null}
</span>
))}
</Tag>
</>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/texto-reveladoEjemplo de uso de Texto que se revela
Después de copiar word-reveal.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { WordReveal } from "./word-reveal";
export default function Ejemplo() {
return <WordReveal text="Ejemplo" once />;
}Propiedades de WordReveal
WordReveal acepta 6 propiedades; 1 es obligatoria.
| Propiedad | Tipo | Descripción |
|---|---|---|
text | string | Obligatoria. |
as? | ElementType | Etiqueta HTML que se renderiza. |
stagger? | number | Milisegundos entre palabras. |
once? | boolean | Si es false, repite la animación cada vez que vuelve a entrar en pantalla. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Texto que se revela 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 |
|---|---|
--gcrv-stagger | 60ms |
Accesibilidad y rendimiento
El archivo tiene 68 líneas (3 KB) y no necesita @keyframes. Esto es lo que hace Texto que se revela por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- 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 título cuyas palabras suben una a una desde una máscara, con un leve desenfoque, cuando entran en pantalla. El texto es visible desde el servidor y solo se oculta cuando el componente ya está listo para revelarlo, así que no se pierde sin JavaScript.
Incluye
Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.
- Pausa entre palabras configurable (stagger)
- Una vez o cada vez que reaparece (once)
- Etiqueta configurable (as): h1, h2, p…
- Respeta prefers-reduced-motion
Cómo usarlo
Pasa el texto en text y elige la etiqueta con as. Estilos de tipografía por style o className.