Ir al contenido
Evaluar mi proyecto
Componentes · Texto y animacionesDisponible

Texto rotativo

Una palabra de la frase cambia: sube, se desenfoca y la siguiente entra.

Hecho para
equipos ágiles, estudios creativos, fundadores, tiendas en línea

Código completo

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

word-rotator.tsx81 líneas
Descargar
"use client";

/**
 * Palabra que cambia dentro de una frase: la actual sube y se desenfoca mientras entra la siguiente, y el ancho se ajusta con suavidad.
 * Se pausa al pasar el puntero o enfocar. Con movimiento reducido solo cambia, sin deslizar.
 *
 *   <h1>Hecho para <WordRotator words={["equipos", "estudios", "fundadores"]} /></h1>
 *
 * Los lectores de pantalla leen todas las palabras una sola vez, en lugar de oír cada cambio.
 */

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

export type WordRotatorProps = {
  words: string[];
  /** Milisegundos entre palabras. */
  interval?: number;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcrw-root { position: relative; display: inline-block; height: 1.3em; margin-bottom: -.12em; padding-bottom: .12em; overflow: hidden; vertical-align: bottom; line-height: 1.3; white-space: nowrap; transition: width .6s cubic-bezier(.16, 1, .3, 1); }
.gcrw-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.gcrw-word { position: absolute; top: 0; left: 0; display: inline-block; }
.gcrw-word[data-state="in"] { position: relative; transform: none; opacity: 1; filter: none; transition: transform .7s cubic-bezier(.16, 1, .3, 1), opacity .5s, filter .6s; }
.gcrw-word[data-state="out"] { transform: translateY(-105%); opacity: 0; filter: blur(7px); transition: transform .7s cubic-bezier(.16, 1, .3, 1), opacity .4s, filter .5s; }
.gcrw-word[data-state="idle"] { transform: translateY(105%); opacity: 0; filter: blur(7px); transition: none; }
@media (prefers-reduced-motion: reduce) { .gcrw-root { transition: none; } .gcrw-word[data-state] { transform: none !important; filter: none !important; transition: opacity .2s !important; } .gcrw-word[data-state="out"] { opacity: 0; } }
`;

export function WordRotator({ words, interval = 2400, className, style }: WordRotatorProps) {
  const [state, setState] = useState({ index: 0, prev: -1 });
  const [widths, setWidths] = useState<number[]>([]);
  const [paused, setPaused] = useState(false);
  const refs = useRef<(HTMLSpanElement | null)[]>([]);

  // Measure every word, again whenever fonts load or the text size changes.
  useEffect(() => {
    const measure = () => setWidths(refs.current.map((element) => element?.scrollWidth ?? 0));
    const observer = new ResizeObserver(measure);
    refs.current.forEach((element) => element && observer.observe(element));
    return () => observer.disconnect();
  }, [words]);

  useEffect(() => {
    if (paused || words.length < 2) return;
    const timer = window.setInterval(() => setState((current) => ({ index: (current.index + 1) % words.length, prev: current.index })), interval);
    return () => window.clearInterval(timer);
  }, [paused, words.length, interval]);

  const width = widths[state.index];

  return (
    <>
      <style>{CSS}</style>
      <span
        className={["gcrw-root", className].filter(Boolean).join(" ")}
        style={{ ...style, width: width ? `${width}px` : undefined }}
        onMouseEnter={() => setPaused(true)}
        onMouseLeave={() => setPaused(false)}
        onFocus={() => setPaused(true)}
        onBlur={() => setPaused(false)}
      >
        <span className="gcrw-sr">{words.join(", ")}</span>
        {words.map((word, index) => (
          <span
            key={word}
            ref={(element) => { refs.current[index] = element; }}
            className="gcrw-word"
            aria-hidden="true"
            data-state={index === state.index ? "in" : index === state.prev ? "out" : "idle"}
          >
            {word}
          </span>
        ))}
      </span>
    </>
  );
}

Ú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-rotativo

Ejemplo de uso de Texto rotativo

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

ejemplo.tsx5 líneas
import { WordRotator } from "./word-rotator";

export default function Ejemplo() {
  return <WordRotator />;
}

Propiedades de WordRotator

WordRotator acepta 4 propiedades; 1 es obligatoria.

PropiedadTipoDescripción
wordsstring[]Obligatoria.
interval?numberMilisegundos entre palabras.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 81 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Texto rotativo 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-*.
  • 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

Una palabra que cambia dentro de una frase. La actual sube y se desenfoca mientras entra la siguiente, y el ancho se ajusta con suavidad para que el texto de alrededor no salte. Se pausa al pasar el puntero o enfocar.

Incluye

Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.

  • Cualquier número de palabras
  • Intervalo configurable
  • Ancho animado según la palabra actual
  • Los lectores de pantalla leen la lista una sola vez

Cómo usarlo

Úsalo dentro de un título: <h1>Hecho para <WordRotator words={[…]} /></h1>. Hereda tipografía y tamaño del texto que lo rodea.