Une bibliothèque pensée pour les designers pressés

React + MotionTexte et défilement

Titre qui se réécrit

Un grand titre tape sa fin lettre par lettre, la sélectionne comme dans un éditeur puis change de phrase.

Réglages

65ms

Code

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 = "Une bibliothèque pensée pour",
  phrases = "les designers pressés, les petites équipes, votre prochain lancement, les gens attentifs au détail",
  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 ? "Reprendre l’animation" : "Mettre l’animation en pause"}
        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 pour l’IA

Collez-le dans ChatGPT, Claude ou Cursor : le bloc sera adapté à votre projet avec ces réglages, même sans React.

Plus dans Texte et défilement

Où l’utiliser

Un effet machine à écrire en React convient à l’en-tête d’un site qui parle à plusieurs publics : un outil « pensé pour les freelances, les petites équipes, votre prochain lancement », ou un portfolio qui annonce « je conçois pour… ». Le début reste fixe pendant que la fin s’écrit, se sélectionne et change, comme une vraie retouche.

Des phrases de longueur proche évitent que le titre ne saute. Trois à cinq suffisent largement. Un toucher sur le texte le met en pause, ce qu’apprécient ceux qui veulent lire tranquillement. Avec l’option de réduction des animations, chaque phrase apparaît entière.