Ir al contenido
Evaluar mi proyecto
Componentes · ScrollDisponible

Texto que se ilumina palabra a palabra

Cada palabra pasa de gris tenue a tinta según el scroll; el texto completo es legible para lectores de pantalla desde el inicio.

Desplázate ↓

Código completo

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

scroll-palabras.tsx70 líneas
Descargar
"use client";

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

/** Párrafo que se ilumina palabra a palabra con el scroll: cada palabra pasa de gris tenue a tinta según su posición en pantalla. El texto completo es legible para lectores de pantalla desde el inicio. Autocontenido en su panel. React + TypeScript, sin dependencias. */
export type ScrollPalabrasProps = {
  text?: string;
  height?: number;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcspa-root { box-sizing: border-box; position: relative; width: 100%; height: var(--h, 460px); overflow-y: auto; overflow-x: hidden; background: #faf8f3; color: #17150f; font: 400 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; scrollbar-width: thin; outline: 0; }
.gcspa-root *, .gcspa-root *::before, .gcspa-root *::after { box-sizing: border-box; }
.gcspa-root:focus-visible { box-shadow: inset 0 0 0 2px #17150f; }
.gcspa-track { height: calc(var(--h, 460px) * 2.4); }
.gcspa-stage { position: sticky; top: 0; display: grid; align-content: center; height: var(--h, 460px); padding: 0 clamp(20px, 6vw, 72px); }
.gcspa-p { margin: 0; max-width: 24ch; font: 500 clamp(30px, 5.4vw, 64px)/1.12 "Newsreader", Georgia, ui-serif, serif; letter-spacing: -.03em; }
.gcspa-w { display: inline; opacity: calc(.14 + var(--k, 0) * .86); transition: opacity .15s linear; }
.gcspa-hint { position: absolute; left: clamp(20px, 6vw, 72px); bottom: 14px; font-size: 12px; color: #8c8573; }
@media (prefers-reduced-motion: reduce) { .gcspa-w { opacity: 1 !important; transition: none; } }`;

const DEFAULT = "No vendemos horas de programación. Entendemos cómo trabaja tu empresa y construimos el software que le queda a la medida, para que tu operación no tenga que parecerse a ningún sistema genérico.";

export function ScrollPalabras({ text = DEFAULT, height = 460, className = "", style }: ScrollPalabrasProps) {
  const ref = useRef<HTMLDivElement>(null);
  const words = text.split(" ");

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf = 0;
    const update = () => {
      raf = 0;
      const max = el.scrollHeight - el.clientHeight;
      const p = max > 0 ? el.scrollTop / max : 0;
      const spans = el.querySelectorAll<HTMLElement>(".gcspa-w");
      const n = spans.length;
      spans.forEach((span, i) => {
        const start = (i / n) * 0.8;
        const k = Math.max(0, Math.min(1, (p - start) / 0.06));
        span.style.setProperty("--k", String(k));
      });
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    update();
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => { el.removeEventListener("scroll", onScroll); cancelAnimationFrame(raf); };
  }, [text]);

  return (
    <div ref={ref} className={`gcspa-root ${className}`} style={{ "--h": `${height}px`, ...style } as CSSProperties} tabIndex={0} role="region" aria-label="Texto que se ilumina al desplazar">
      <style>{CSS}</style>
      <div className="gcspa-track">
        <div className="gcspa-stage">
          <p className="gcspa-p" aria-label={text}>
            {words.map((word, i) => (
              <span key={i} className="gcspa-w" aria-hidden="true">
                {word}{" "}
              </span>
            ))}
          </p>
          <span className="gcspa-hint">Desplázate ↓</span>
        </div>
      </div>
    </div>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

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

Ejemplo de uso de Texto que se ilumina palabra a palabra

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

ejemplo.tsx5 líneas
import { ScrollPalabras } from "./scroll-palabras";

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

Propiedades de ScrollPalabras

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

PropiedadTipoDescripción
text?stringOpcional.
height?numberOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Texto que se ilumina palabra a palabra 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
--h460px
--k0

Accesibilidad y rendimiento

El archivo tiene 70 líneas (3 KB) y no necesita @keyframes. Esto es lo que hace Texto que se ilumina palabra a palabra por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Declara roles ARIA para lectores de pantalla: region.
  • 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 18 y 19 · Next.js (componente de cliente)