Skip to content
← 1001 Widgets
N° 016 · ●○○

Typewriter effect

A text that types itself letter by letter, erases itself and moves on to the next sentence. The right exercise to learn to chain steps over time.

JS
What you'll learn
  • Chain steps with setTimeout
  • Cut a sentence with slice
  • Respect prefers-reduced-motion
Why this language

Pourquoi une machine à écrire ? Parce que c'est l'effet le plus demandé sur les pages d'accueil, et qu'il apprend une chose essentielle : faire avancer un état pas à pas dans le temps.

Le principe : un index de lettre qui monte, puis qui redescend, puis une phrase suivante. Chaque pas programme le suivant avec setTimeout.

Pourquoi en JavaScript simple ? Deux compteurs et un minuteur. Pas besoin de bibliothèque pour un effet qui tient en quinze lignes.

How it works
  1. LE HTML

    Le texte et un curseur séparé, pour que le curseur clignote sans être réécrit.

    <p class="ecran" aria-live="polite"><span id="txt"></span><span class="curseur" aria-hidden="true"></span></p>
  2. L'ÉTAT

    const PHRASES = ['Bonjour, je suis Lass.', 'Je construis des sites qui bougent.'];
    let i = 0;         // la phrase en cours
    let j = 0;         // le nombre de lettres affichées
    let efface = false;
    const txt = document.getElementById('txt');
  3. UN PAS

    On ajoute ou on retire une lettre, puis on programme le pas suivant.

    function pas() {
      const phrase = PHRASES[i];
      j += efface ? -1 : 1;
      txt.textContent = phrase.slice(0, j);
      if (!efface && j === phrase.length) { efface = true; return setTimeout(pas, 1400); }
      if (efface && j === 0) { efface = false; i = (i + 1) % PHRASES.length; }
      setTimeout(pas, efface ? 35 : 70 + Math.random() * 40);
    }
  4. LE CURSEUR

    steps(1) donne un clignotement net, sans fondu.

    .curseur { display: inline-block; width: .5em; height: .9em; background: currentColor; animation: clignote 1s steps(1) infinite; }
    @keyframes clignote { 50% { opacity: 0; } }
  5. LANCER, SAUF SI L'UTILISATEUR PRÉFÈRE LE CALME

    if (matchMedia('(prefers-reduced-motion: reduce)').matches) txt.textContent = PHRASES[0];
    else pas();
Pitfalls

PIÈGE 1 : setInterval à vitesse fixe

Pitfall

Mauvais : un setInterval de 100 ms. L'écriture paraît mécanique, et on ne peut pas faire de pause à la fin d'une phrase.

Fix

Correct : setTimeout à chaque pas, avec un délai qui varie un peu et une vraie pause en fin de phrase.

PIÈGE 2 : Le texte invisible pour les lecteurs d'écran

Pitfall

Mauvais : annoncer chaque lettre une par une avec aria-live. C'est insupportable à écouter.

Fix

Correct : aria-live="polite" sur un conteneur, ou mieux, la phrase complète en texte masqué visuellement.

PIÈGE 3 : Deux boucles en même temps

Pitfall

Mauvais : relancer pas() à chaque clic sur Rejouer. Deux machines écrivent dans le même texte.

Fix

Correct : garder l'identifiant du setTimeout et appeler clearTimeout avant de relancer.

PERSONNALISER
  • Une vitesse réglable avec un <input type="range"> lu à chaque pas.
  • Un bouton pause qui arrête la planification sans perdre la position.
  • Une couleur différente pour un mot clé : découpez la phrase en morceaux avec chacun son style.
OPTIMISATION
  • Avec prefers-reduced-motion, affichez directement la phrase entière.
  • Mettez la boucle en pause quand l'onglet est caché, avec l'événement visibilitychange.
VERS REACT

Les compteurs i, j et efface deviennent un seul useState ou un useReducer.

Le useEffect programme le pas suivant et renvoie clearTimeout comme nettoyage.

Le texte affiché se calcule pendant le rendu : phrase.slice(0, j).

Variants
Comments
0/1000

Loading comments…

Audience measurement

With your consent, this site measures its traffic with Google Analytics and the Meta pixel, via Google Tag Manager. Nothing is loaded until you choose, and you can change your mind at any time with the "Cookies" link at the bottom of the page.