Ir al contenido
Evaluar mi proyecto
Componentes · Texto y animacionesDisponible

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.

Demostración interactiva

Código completo

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

word-reveal.tsx68 líneas
Descargar
"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.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/texto-revelado

Ejemplo 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.

ejemplo.tsx5 líneas
import { WordReveal } from "./word-reveal";

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

Propiedades de WordReveal

WordReveal acepta 6 propiedades; 1 es obligatoria.

PropiedadTipoDescripción
textstringObligatoria.
as?ElementTypeEtiqueta HTML que se renderiza.
stagger?numberMilisegundos entre palabras.
once?booleanSi es false, repite la animación cada vez que vuelve a entrar en pantalla.
className?stringOpcional.
style?CSSPropertiesOpcional.

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.

VariableValor por defecto
--gcrv-stagger60ms

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".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React · Next.js (quita la directiva use client fuera de Next.js) · Sin Tailwind

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.