L'effet machine à écrire est l'équivalent numérique de voir quelqu'un réfléchir à voix haute. Le texte apparaît caractère par caractère, comme si une main réelle frappait de vraies touches. On le retrouve dans les sections hero des sites de portfolio, dans les émulateurs de terminal basés sur le navigateur, et dans les fenêtres de chat des assistants IA qui veulent prouver qu'ils sont en train de « taper » une réponse plutôt que de simplement récupérer un bloc de texte préfabriqué. Bien réalisé, cela crée de l'anticipation. Mal réalisé, cela donne l'impression d'une imprimante en panne en 1987.

Pourquoi ce modèle persiste

Les ordinateurs délivrent l'information instantanément. Les humains, non. L'écart entre ces deux vitesses est utile. L'effet machine à écrire comble ce fossé en simulant le rythme humain. Sur une landing page, il peut attirer l'œil sur un titre mot après mot, afin que les visiteurs lisent réellement la proposition de valeur au lieu de simplement la survoler. Dans un émulateur de terminal, il vend l'illusion que les commandes s'exécutent en temps réel. Dans une interface de chatbot, le rythme signale qu'une réponse est générée à la volée plutôt que récupérée dans une base de données.

Mais l'effet ne fonctionne que si la mécanique respecte l'utilisateur. Un tic-tac métronomique et plat d'intervalles identiques semble robotique. Pire, une implémentation qui ignore les technologies d'assistance peut transformer une jolie fioriture visuelle en une barrière frustrante. L'objectif n'est pas de ralentir l'utilisateur, mais d'ajouter juste assez de friction pour que l'interface semble vivante.

Construisez-le avec un setTimeout récursif

Commencez par setTimeout, et ne touchez pas à setInterval. La différence est plus importante qu'il n'y paraît.

setInterval est têtu. Il s'exécute toutes les n millisecondes, peu importe ce qui se passe ailleurs dans votre script ou dans le thread principal du navigateur. Si votre logique nécessite une pause de 50 millisecondes entre les frappes ordinaires, mais une pause de 150 millisecondes après une ponctuation, setInterval ne peut pas s'adapter. Vous finissez par l'envelopper dans une logique conditionnelle supplémentaire, à lutter contre des conditions de concurrence, et finalement à effacer et réinitialiser l'intervalle si souvent que le code devient un cauchemar de gestion d'état. Les intervalles fixes échouent dès que vous avez besoin de vitesses variables.

Le setTimeout récursif corrige cela en laissant chaque étape décider des règles de l'étape suivante. Considérez cela comme une petite machine à états. Vous maintenez quelques variables : la chaîne actuelle, l'index du caractère actuel, un flag booléen pour savoir si vous tapez ou si vous effacez, et un compteur textIndex pour pouvoir boucler sur plusieurs chaînes. La fonction ajoute un caractère, vérifie où elle en est dans la phrase, puis programme sa propre invocation suivante avec un délai qui correspond au contexte.

Par exemple, vous pourriez taper la plupart des caractères à 50 millisecondes, ralentir à 150 millisecondes après une virgule, et faire une pause de 800 millisecondes à la fin d'une phrase complète avant de passer en mode suppression. Vous ne pouvez pas faire cela proprement avec setInterval. Avec un setTimeout récursif, la logique est simple :

if typing:
  append next character
  if at end of string:
    switch to pause mode
    schedule next call after 1000ms
if deleting:
  remove last character
  if string empty:
    increment textIndex
    load next string
    switch to typing mode

Cette structure rend également le nettoyage trivial. Stockez l'ID du timeout. Lorsque le composant se démonte ou que l'utilisateur navigue ailleurs, appelez clearTimeout une seule fois. Aucun intervalle orphelin ne tournera en arrière-plan.

Le curseur devrait clignoter de manière autonome

Le curseur clignotant est un détail visuel, pas une préoccupation de données. Gardez-le hors de votre moteur d'état JavaScript. Utilisez une animation CSS distincte attachée à un pseudo-élément ::after ou à un <span dédié placé à la fin de votre conteneur de texte.

Une simple @keyframes blink alternant l' opacity ou la border-color avec un timing step-end vous donne une impulsion nette et fluide qui s'exécute sur le compositor. JavaScript n'a pas à micro-gérer la visibilité du curseur. Si vous basculez les propriétés d'affichage à l'intérieur de votre récursion setTimeout, vous forcez des recalculs de style inutiles à chaque caractère. Laissez le CSS gérer l'esthétique. Laissez le JavaScript gérer la séquence.

Boucler à travers plusieurs chaînes est simple avec un compteur textIndex. Stockez vos chaînes dans un tableau. Lorsque l'animation termine sa phase de suppression et que le conteneur est vide, incrémentez textIndex modulo la longueur du tableau, réinitialisez le pointeur de caractère à zéro, et recommencez à taper. C'est ainsi que les sites de portfolio font défiler les rôles — ["Developer", "Designer", "Writer"] — sans jamais recharger la page.

Les erreurs qui brisent l'illusion

Trois erreurs reviennent sans cesse dans les implémentations amateurs.

Utilisation de setInterval. Nous avons déjà abordé le problème de la vitesse variable, mais il existe un problème plus subtil. Si votre mise à jour du DOM subit un retard — par exemple, parce que le navigateur effectue un changement de mise en page (layout shift) — setInterval continue de s'exécuter. Vous pouvez vous retrouver avec des écritures qui se chevauchent, des caractères en double ou des écritures qui se produisent plus rapidement que le navigateur ne peut les afficher. Un setTimeout récursif attend que l'étape actuelle soit terminée avant d'envisager la suivante.

Oublier d'échapper le HTML. Si vos chaînes sources contiennent des chevrons et que vous injectez du contenu via innerHTML caractère par caractère, vous allez couper les balises en deux. Le navigateur voit <, puis <s, puis <st. Cela empêche une analyse syntaxique (parsing) correcte des balises et peut laisser des nœuds DOM corrompus ou des cascades de styles inattendues. Si vous voulez que les caractères apparaissent littéralement, échappez-les d'abord ou, mieux encore, écrivez dans textContent plutôt que dans innerHTML. Si vous avez réellement besoin de <span> stylisés à l'intérieur du texte dactylographié, pré-traitez la chaîne afin de savoir exactement où les balises commencent et se terminent avant de lancer la boucle de caractères.

Ignorer l'accessibilité. Les lecteurs d'écran n'apprécient pas d'être lus lettre par lettre. À mesure que votre script ajoute chaque nouveau caractère au DOM, certaines technologies d'assistance annoncent à nouveau l'intégralité du nœud, provoquant une rafale saccadée de mots partiels. C'est un cauchemar pour toute personne s'appuyant sur la navigation auditive. La solution n'est pas compliquée : ajoutez un aria-label au conteneur qui contient le texte complet et final. Vous pouvez également masquer entièrement l'élément animé aux technologies d'assistance avec aria-hidden="true" et fournir une copie statique visuellement masquée pour les lecteurs d'écran. Dans tous les cas, donnez aux utilisateurs la phrase complète dès le départ au lieu de les forcer à assister à votre performance.

Comment perfectionner votre version

Une fois que la boucle principale fonctionne correctement, vous pouvez ajouter des fonctionnalités supplémentaires. Mais résistez à la tentation de les ajouter tant que les fondamentaux ne sont pas solides.

Effets sonores de frappe. Un clic subtil à chaque caractère peut être satisfaisant, mais l'audio sur les pages web est un champ de mines. Utilisez la Web Audio API ou un élément Audio léger avec un tampon court. Variez légèrement la vitesse de lecture — entre 0,95 et 1,05 — pour que les clics identiques ne sonnent pas de manière synthétique. Respectez toujours les politiques d'autoplay du navigateur et prévoyez un bouton de coupure du son. Rien ne fait fuir les utilisateurs plus vite qu'une page de portfolio dont le son n'est pas coupé et qui joue des bruits de dactylographie à 9 heures du matin.

Saisie multi-ligne. Les vraies fenêtres de terminal gèrent le retour à la ligne. Si votre texte franchit un saut de ligne, un simple curseur border-right sautera de manière maladroite, à moins que votre mise en page ne soit prévisible. Séparez les chaînes par des caractères de saut de ligne et rendez chaque ligne dans son propre <span>, ou utilisez un pseudo-élément positionné qui suit la fin du contenu. Faites attention au retour à la ligne du texte ; un curseur implémenté comme une bordure en ligne peut se détacher du texte si la largeur du conteneur change. Envisagez d'utiliser white-space: pre-wrap et une police à chasse fixe (monospaced) pour les styles de terminal, car les caractères à largeur fixe rendent le calcul de la position du curseur bien plus prévisible.

Rendu Markdown en temps réel. C'est là que les choses se compliquent. Si vous tapez **bold**, vous avez le choix : afficher les astérisques littéralement tels qu'ils apparaissent, ou les convertir en style gras à la volée. Si vous choisissez la seconde option, passer de textContent à innerHTML en cours de route signifie que les limites de vos nœuds de texte changent. La position du curseur devient un casse-tête de gestion car les balises HTML déplacent l'arbre DOM sous vos pieds. Une approche plus sûre consiste à taper la chaîne Markdown brute normalement, puis à déclencher une passe de rendu une fois que la chaîne complète est à l'écran. Si vous avez réellement besoin d'un formatage en direct, maintenez deux couches : un tampon de frappe caché et une superposition visuelle analysée (parsed).

Effets d'effacement. Supprimer du texte ne signifie pas forcément effacer un caractère à la fois avec la touche retour arrière. Vous pouvez simuler une réinitialisation de type « tout sélectionner, puis supprimer » qui vide le champ instantanément avant que la chaîne suivante ne soit tapée. Cela semble un peu froid. Alternativement, un effacement lent à 30 millisecondes par caractère crée de la tension. Mélangez les deux : effacez une faute de frappe rapidement, faites une pause, puis reprenez l'effacement à vitesse normale. Cette variation renforce l'aspect humain de l'effet.

L'essentiel à retenir

Un effet de machine à écrire est l'un de ces artifices d'interface utilisateur qui semblent triviaux en surface et ne révèlent leur complexité qu'une fois construits. Commencez par