Una librería pensada para diseñadores con prisa

React + MotionTexto y scroll

Titular que se reescribe

Un titular grande escribe su final letra a letra, lo selecciona como en un editor y pasa a otra frase.

Ajustes

65ms

Código

import { useEffect, useMemo, useState } from "react";
import { motion, useReducedMotion } from "framer-motion";

interface TypewriterRotatorProps {
  prefix?: string;
  /** Frases separadas por comas. */
  phrases?: string;
  /** Milisegundos por letra al escribir. */
  speed?: number;
  caret?: string;
}

export default function TypewriterRotator({
  prefix = "Una librería pensada para",
  phrases = "diseñadores con prisa, equipos pequeños, tu próximo lanzamiento, gente que cuida el detalle",
  speed = 65,
  caret = "#7c5cff",
}: TypewriterRotatorProps) {
  const list = useMemo(
    () =>
      phrases
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean),
    [phrases],
  );
  const [index, setIndex] = useState(0);
  const [length, setLength] = useState(() => list[0]?.length ?? 0);
  const [selecting, setSelecting] = useState(false);
  const [paused, setPaused] = useState(false);
  const reduce = useReducedMotion();
  const word = list.length ? (list[index % list.length] ?? "") : "";

  useEffect(() => {
    if (paused || !word) return;
    let delay: number;
    let next: () => void;
    if (reduce) {
      delay = 2600;
      next = () => setIndex((i) => i + 1);
    } else if (selecting) {
      delay = 480;
      next = () => {
        setSelecting(false);
        setLength(0);
        setIndex((i) => i + 1);
      };
    } else if (length < word.length) {
      delay = length === 0 ? 320 : speed * (0.6 + Math.random() * 0.8);
      next = () => setLength(length + 1);
    } else {
      delay = 1900;
      next = () => setSelecting(true);
    }
    const t = setTimeout(next, delay);
    return () => clearTimeout(t);
  }, [paused, word, reduce, selecting, length, speed]);

  const shown = reduce ? word : word.slice(0, length);
  const typing = !paused && !reduce && !selecting && length < word.length;

  return (
    <div className="w-full max-w-md">
      <p className="sr-only">
        {prefix} {word}
      </p>
      <button
        type="button"
        onClick={() => setPaused((p) => !p)}
        aria-label={paused ? "Reanudar animación" : "Pausar animación"}
        aria-pressed={paused}
        className="block w-full cursor-pointer text-left text-[34px] font-semibold leading-[1.06] tracking-[-0.035em] sm:text-[42px]"
      >
        <span aria-hidden="true" className="block text-balance text-[#1d1d1f] dark:text-[#f5f5f7]">
          {prefix}
        </span>
        <span aria-hidden="true" className="block min-h-[2.12em]" style={{ color: caret }}>
          <span
            className="rounded-[0.1em] transition-colors duration-150"
            style={{
              background: selecting ? "color-mix(in srgb, " + caret + " 22%, transparent)" : "transparent",
            }}
          >
            {shown}
          </span>
          <motion.span
            className="ml-[0.05em] inline-block h-[0.86em] w-[0.075em] translate-y-[0.12em] rounded-full"
            style={{ background: caret }}
            animate={{ opacity: paused ? 0.3 : typing ? 1 : [1, 1, 0, 0] }}
            transition={
              typing || paused
                ? { duration: 0.15 }
                : { duration: 1, repeat: Infinity, times: [0, 0.5, 0.5, 1] }
            }
          />
        </span>
      </button>
    </div>
  );
}

Prompt para IA

Pégalo en ChatGPT, Claude o Cursor y adaptará el bloque a tu proyecto con estos ajustes, aunque no uses React.

Más en Texto y scroll

Dónde usarlo

Un efecto máquina de escribir en React encaja en la portada de una web que habla a varios públicos a la vez: una herramienta «pensada para freelances, equipos pequeños, tu próximo lanzamiento», o un portfolio que dice «diseño para…». El inicio fijo no se mueve mientras el final se escribe, se selecciona y se cambia, como alguien editando de verdad.

Si las frases tienen una longitud parecida, el titular no baila. Con tres a cinco hay de sobra. Tocar el texto lo pausa, cosa que agradece quien quiere leer con calma. Con reducir movimiento activado, cada frase aparece entera sin teclear.