Ir al contenido
Evaluar mi proyecto
Componentes · Texto y animacionesDisponible

Máquina de escribir

Escribe una frase, la borra y sigue con la siguiente, con cursor parpadeante.

Nosotros diseñamos sistemas.. automatizamos procesos.. integramos tus datos.

Demostración interactiva

Código completo

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

typewriter.tsx80 líneas
Descargar
"use client";

/**
 * Máquina de escribir: escribe una frase, la borra y pasa a la siguiente, con un cursor que parpadea.
 * Con movimiento reducido muestra la primera frase completa y quieta.
 *
 *   <Typewriter phrases={["Diseñamos sistemas.", "Automatizamos procesos.", "Integramos tus datos."]} />
 *
 * Los lectores de pantalla reciben todas las frases una vez, sin oír cada letra.
 */

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

export type TypewriterProps = {
  phrases: string[];
  /** Milisegundos por letra al escribir. */
  typeSpeed?: number;
  /** Milisegundos por letra al borrar. */
  deleteSpeed?: number;
  /** Pausa con la frase completa. */
  pause?: number;
  loop?: boolean;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gctw-root { display: inline; }
.gctw-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.gctw-caret { display: inline-block; width: .09em; height: 1em; margin-left: .06em; vertical-align: -.1em; background: currentColor; animation: gctw-blink 1.05s steps(1) infinite; }
.gctw-caret[data-typing="true"] { animation: none; }
@keyframes gctw-blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .gctw-caret { animation: none; } }
`;

export function Typewriter({ phrases, typeSpeed = 55, deleteSpeed = 28, pause = 1500, loop = true, className, style }: TypewriterProps) {
  const [reduced, setReduced] = useState(false);
  const [view, setView] = useState({ phrase: 0, length: 0, deleting: false });

  useEffect(() => {
    const frame = requestAnimationFrame(() => setReduced(window.matchMedia("(prefers-reduced-motion: reduce)").matches));
    return () => cancelAnimationFrame(frame);
  }, []);

  useEffect(() => {
    if (reduced || !phrases.length) return;
    const text = phrases[view.phrase];
    let delay = view.deleting ? deleteSpeed : typeSpeed;
    let next = view;

    if (!view.deleting && view.length === text.length) {
      if (!loop && view.phrase === phrases.length - 1) return;
      delay = pause;
      next = { ...view, deleting: true };
    } else if (view.deleting && view.length === 0) {
      delay = 380;
      next = { phrase: (view.phrase + 1) % phrases.length, length: 0, deleting: false };
    } else {
      next = { ...view, length: view.length + (view.deleting ? -1 : 1) };
    }

    const timer = window.setTimeout(() => setView(next), delay);
    return () => window.clearTimeout(timer);
  }, [view, reduced, phrases, typeSpeed, deleteSpeed, pause, loop]);

  const shown = reduced ? phrases[0] ?? "" : (phrases[view.phrase] ?? "").slice(0, view.length);
  const typing = !reduced && (!view.deleting ? view.length < (phrases[view.phrase] ?? "").length : view.length > 0);

  return (
    <>
      <style>{CSS}</style>
      <span className={["gctw-root", className].filter(Boolean).join(" ")} style={style}>
        <span className="gctw-sr">{phrases.join(". ")}</span>
        <span aria-hidden="true">{shown}</span>
        <span className="gctw-caret" data-typing={typing} aria-hidden="true" />
      </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-maquina

Ejemplo de uso de Máquina de escribir

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

ejemplo.tsx5 líneas
import { Typewriter } from "./typewriter";

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

Propiedades de Typewriter

Typewriter acepta 7 propiedades; 1 es obligatoria.

PropiedadTipoDescripción
phrasesstring[]Obligatoria.
typeSpeed?numberMilisegundos por letra al escribir.
deleteSpeed?numberMilisegundos por letra al borrar.
pause?numberPausa con la frase completa.
loop?booleanOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 80 líneas (3 KB) y define 1 animación con @keyframes. Esto es lo que hace Máquina de escribir 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-*.
  • 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

Escribe frases con un cursor que parpadea, las borra y pasa a la siguiente en bucle. Las velocidades y la pausa son configurables. Con movimiento reducido muestra la primera frase completa y quieta.

Incluye

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

  • Varias frases en bucle (o una sola vez)
  • Velocidad de escritura, borrado y pausa configurables
  • Cursor que no parpadea mientras escribe
  • Los lectores de pantalla reciben todas las frases una vez

Cómo usarlo

Pasa phrases. El cursor usa el color del texto (currentColor).