Développement Web

Tutoriel : Créer un effet de machine à écrire en JavaScript

11 دقائق للقراءة

Prérequis

  • Niveau : bases JavaScript (Promesses, async/await, classes ES6).
  • Outils : VS Code + Live Server, navigateur moderne.
  • Temps estimé : 1 h.

Pourquoi cet effet ?

L’effet typewriter est un grand classique des hero sections : il capte le regard et raconte une histoire en quelques secondes. Bien utilisé (1 fois sur la page, 3 à 5 mots maximum), il booste l’engagement. Mal utilisé, il fatigue.

Ce que vous allez créer

L’effet de machine à écrire (typewriter effect) affiche un texte caractère par caractère, comme si quelqu’un le tapait en direct. C’est un effet visuel populaire pour les pages d’accueil, les portfolios et les landing pages. Dans ce tutoriel, vous allez construire 3 versions progressives : une basique, une avec suppression et réécriture, et une version réutilisable avec des options configurables.

Version 1 : l’effet typewriter basique

Le principe est simple : on prend une chaîne de caractères, on la découpe, et on ajoute un caractère à la fois dans un élément HTML avec un setTimeout ou setInterval.

Le HTML

<div class="typewriter-container">
  <h1 id="typewriter"></h1>
  <span class="cursor">|</span>
</div>

Le CSS pour le curseur clignotant

.typewriter-container {
  font-family: 'Courier New', monospace;
  font-size: 2rem;
  display: flex;
  align-items: center;
}

.cursor {
  animation: blink 0.7s infinite;
  font-weight: 100;
  margin-left: 2px;
}

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}

Le JavaScript

function typeWriter(élément, text, speed = 80) {
  let index = 0;
  
  function taper() {
    if (index < text.length) {
      élément.textContent += text.charAt(index);
      index++;
      setTimeout(taper, speed);
    }
  }
  
  taper();
}

// Lancer l'effet
const el = document.getElementById('typewriter');
typeWriter(el, 'Bienvenue sur ITSkillsCenter', 80);

Comment ça fonctionne : la fonction taper() s’appelle elle-même via setTimeout à chaque itération. À chaque appel, elle ajoute un caractère au contenu de l’élément et incrémente l’index. Quand l’index atteint la longueur du texte, elle s’arrête.

Version 2 : écrire, effacer et réécrire (effet boucle)

Cette version affiche un mot, l’efface, puis affiche le suivant. Parfait pour afficher plusieurs compétences ou services sur une page d’accueil.

function typewriterLoop(élément, words, typeSpeed = 100, eraseSpeed = 50, pauseTime = 2000) {
  let wordIndex = 0;
  let charIndex = 0;
  let isErasing = false;

  function animate() {
    const currentWord = words[wordIndex];
    
    if (!isErasing) {
      // Mode écriture
      élément.textContent = currentWord.substring(0, charIndex + 1);
      charIndex++;
      
      if (charIndex === currentWord.length) {
        // Mot complet → pause puis effacer
        isErasing = true;
        setTimeout(animate, pauseTime);
        return;
      }
      setTimeout(animate, typeSpeed);
    } else {
      // Mode suppression
      élément.textContent = currentWord.substring(0, charIndex - 1);
      charIndex--;
      
      if (charIndex === 0) {
        // Mot effacé → passer au suivant
        isErasing = false;
        wordIndex = (wordIndex + 1) % words.length;
        setTimeout(animate, 500);
        return;
      }
      setTimeout(animate, eraseSpeed);
    }
  }

  animate();
}

// Utilisation
const el = document.getElementById('typewriter');
typewriterLoop(el, [
  'Développeur Web',
  'Designer UI/UX', 
  'Freelance à Dakar',
  'Passionné de code'
]);

Points clés :

  • wordIndex cycle entre les mots grâce à l’opérateur modulo %
  • L’effacement est plus rapide que l’écriture (50ms vs 100ms) pour un rendu naturel
  • Une pause de 2 secondes après chaque mot complet laisse le temps de lire

Version 3 : composant réutilisable avec Promesses

Cette version utilise les Promesses et async/await pour un code plus propre et plus facile à étendre.

class Typewriter {
  constructor(élément, options = {}) {
    this.élément = élément;
    this.speed = options.speed || 80;
    this.deleteSpeed = options.deleteSpeed || 40;
    this.pauseTime = options.pauseTime || 1500;
  }

  // Attendre un certain temps
  wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Écrire un texte caractère par caractère
  async type(text) {
    for (const char of text) {
      this.élément.textContent += char;
      await this.wait(this.speed + Math.random() * 50); // Variation pour un effet naturel
    }
    return this;
  }

  // Effacer n caractères
  async delete(count) {
    for (let i = 0; i < count; i++) {
      this.élément.textContent = this.élément.textContent.slice(0, -1);
      await this.wait(this.deleteSpeed);
    }
    return this;
  }

  // Effacer tout
  async deleteAll() {
    await this.delete(this.élément.textContent.length);
    return this;
  }

  // Pause
  async pause(ms) {
    await this.wait(ms || this.pauseTime);
    return this;
  }

  // Boucle sur plusieurs textes
  async loop(texts) {
    while (true) {
      for (const text of texts) {
        await this.type(text);
        await this.pause();
        await this.deleteAll();
        await this.wait(300);
      }
    }
  }
}

// Utilisation simple et lisible
const tw = new Typewriter(document.getElementById('typewriter'), {
  speed: 70,
  deleteSpeed: 30,
  pauseTime: 2000
});

tw.loop([
  'Je crée des sites web.',
  'Je développe des applications.',
  'Je transforme vos idées en code.'
]);

Pourquoi cette version est meilleure

  • Lisible : le chaînage avec async/await lit comme une recette étape par étape
  • Naturelle : Math.random() * 50 ajoute une légère variation de vitesse, comme un vrai humain
  • Extensible : ajoutez facilement des méthodes (highlight(), moveTo(), etc.)
  • Réutilisable : un seul new Typewriter() par élément

Ajouter un curseur CSS personnalisé

Pour un curseur plus réaliste que le simple |, utilisez un ::after pseudo-élément :

#typewriter::after {
  content: '';
  display: inline-block;
  width: 3px;
  height: 1.2em;
  background-color: #333;
  margin-left: 4px;
  vertical-align: text-bottom;
  animation: blink 0.7s step-end infinite;
}

@keyframes blink {
  50% { opacity: 0; }
}

Avec cette approche, pas besoin d’élément HTML supplémentaire pour le curseur.

Erreurs fréquentes

Boucle infinie qui consomme du CPU

Cause : on appelle setTimeout trop court (10ms) dans un while.
Solution : minimum 30-50ms par caractère (vitesse réaliste de saisie). En dessous, l’œil ne suit pas.

Effet ignoré par les utilisateurs sensibles

Cause : animations toujours actives.
Solution : testez matchMedia('(prefers-reduced-motion: reduce)').matches et affichez le texte final sans animation dans ce cas.

Curseur qui « saute » de position

Cause : curseur dans un élément séparé qui ne suit pas la fin du texte.
Solution : utilisez ::after sur l’élément texte (inclus dans cet article) ou un layout flex stable.

Layout shift quand le mot le plus long apparaît

Cause : la hauteur de la ligne change selon le contenu.
Solution : donnez une min-height au conteneur égale à la hauteur d’une ligne du plus long mot.

Exercice : créez votre propre effet

Créez une page HTML avec un effet typewriter qui :

  1. Écrit « Bonjour, je suis  » puis enchaîne avec votre nom en couleur différente
  2. Pause 3 secondes
  3. Efface uniquement votre nom
  4. Écrit votre titre professionnel à la place
  5. Recommence en boucle

Indice : utilisez deux éléments <span> côte à côte — un fixe pour « Bonjour, je suis  » et un dynamique pour le texte qui change. Appliquez la classe Typewriter uniquement sur le span dynamique.

Lectures complémentaires

Étape 1 : poser le contexte UX et choisir l’effet

L’effet machine à écrire attire le regard sur un titre, un slogan ou une accroche. Avant d’ouvrir un éditeur, posez l’objectif. À Dakar, Abidjan ou Cotonou, un freelance qui anime sa landing page avec ce micro-effet augmente le temps passé au-dessus de la ligne de flottaison. Ne l’utilisez qu’une fois par page, sinon l’attention se dilue.

Définissez la phrase à animer, la durée par caractère (40 à 80 ms) et le délai entre deux phrases si vous bouclez. Notez aussi l’option de curseur clignotant. Ces trois variables guideront tout le code des étapes suivantes.

Étape 2 : préparer le HTML minimal et accessible

Créez un fichier index.html avec un conteneur dédié. Le rôle ARIA aria-live="polite" permet aux lecteurs d’écran de relayer le texte sans interrompre l’utilisateur, ce qui est crucial pour respecter l’accessibilité WCAG 2.2.

<h1 id="typewriter" aria-live="polite"></h1>
<span id="cursor">|</span>
<script src="typewriter.js" defer></script>

Sauvegardez puis ouvrez le fichier dans un navigateur. À ce stade, vous voyez le curseur statique. C’est le signal que la structure est correcte avant d’ajouter le JavaScript.

Étape 3 : écrire la fonction typewriter en JavaScript pur

Pas besoin de framework. Une boucle setInterval ou une chaîne de promesses asynchrones suffit. La version async/await est plus lisible et plus facile à maintenir, c’est celle que nous recommandons à nos lecteurs au Sénégal et en Côte d’Ivoire.

const sleep = ms => new Promise(r => setTimeout(r, ms));
async function type(text, target, speed = 60) {
  for (const char of text) {
    target.textContent += char;
    await sleep(speed);
  }
}
type("Bienvenue sur mon site", document.getElementById('typewriter'));

Rechargez la page. Le titre se compose lettre par lettre. Si rien ne s’affiche, ouvrez la console (F12) et vérifiez l’absence d’erreur de sélecteur ; un id mal orthographié casse silencieusement l’effet.

Étape 4 : ajouter le curseur clignotant en CSS

Le curseur figé n’est pas convaincant. Une animation CSS de 0,7 seconde donne le rythme attendu sans alourdir le JavaScript. Préférez opacity à visibility pour bénéficier de l’accélération matérielle.

#cursor {
  display: inline-block;
  font-weight: 700;
  animation: blink 0.7s steps(2) infinite;
}
@keyframes blink { to { opacity: 0; } }

En rechargeant, le curseur clignote en parallèle de la frappe. Vous obtenez la signature visuelle des terminaux Linux qui plaît aux développeurs francophones d’Afrique de l’Ouest.

Étape 5 : faire boucler plusieurs phrases

Une seule phrase fatigue vite. Faites tourner trois ou quatre messages en boucle, avec une phase d’effacement caractère par caractère. C’est le pattern utilisé par les sites de freelances de Dakar pour valoriser plusieurs spécialités.

const phrases = ["Développeur Full-Stack", "Formateur React", "Consultant DevOps"];
async function loop(target) {
  let i = 0;
  while (true) {
    await type(phrases[i], target);
    await sleep(1500);
    while (target.textContent.length) {
      target.textContent = target.textContent.slice(0, -1);
      await sleep(30);
    }
    i = (i + 1) % phrases.length;
  }
}

Le résultat enchaîne les phrases en boucle infinie. Pensez à cancelAnimationFrame ou un drapeau d’arrêt si l’utilisateur quitte la zone visible : utile pour préserver la batterie sur smartphones, majoritaires au Sénégal.

Étape 6 : respecter prefers-reduced-motion

Certains utilisateurs paramètrent leur OS pour réduire les animations (vertige, migraines). Une bonne pratique professionnelle consiste à désactiver l’effet pour eux et afficher la phrase complète immédiatement.

const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) {
  document.getElementById('typewriter').textContent = phrases[0];
} else {
  loop(document.getElementById('typewriter'));
}

Testez en activant l’option « Réduire les animations » dans les paramètres système. La phrase doit apparaître instantanément. C’est un signal de conformité WCAG 2.2 critère 2.3.3.

Étape 7 : optimiser pour les connexions africaines

Sur une 3G partagée à Saint-Louis ou à Bobo-Dioulasso, chaque kilooctet compte. Inlinez le script si la page est statique, ou compressez-le avec esbuild --minify. Le script complet pèse moins de 600 octets gzippés, négligeable face au gain UX.

Évitez les bibliothèques de 30 ko (Typed.js, TypeIt) si vous n’utilisez qu’un effet simple. Le code maison ci-dessus couvre 95 % des cas et reste maintenable. Vérifiez le poids final avec l’onglet Network de Chrome DevTools.

Étape 8 : intégrer dans WordPress, React ou Vue

Sur WordPress (très répandu chez les freelances francophones), placez le bloc HTML dans un widget « HTML personnalisé » et chargez le script via wp_enqueue_script dans functions.php. Sur React, encapsulez la logique dans un hook useTypewriter avec useEffect pour gérer le démontage proprement.

function useTypewriter(phrases, speed = 60) {
  const [text, setText] = useState('');
  useEffect(() => {
    let cancelled = false;
    (async () => { /* boucle similaire */ })();
    return () => { cancelled = true; };
  }, []);
  return text;
}

Le hook renvoie une chaîne réactive que vous bindez dans le JSX. Le drapeau cancelled évite les fuites mémoire quand le composant est démonté pendant la frappe.

Étape 9 : aller plus loin avec un budget rendu

Pour des animations plus riches (sous-titres synchronisés, plusieurs lignes), regardez aussi notre tutoriel Automatiser ses tweets en TypeScript qui explore la planification asynchrone, ou notre guide Optimiser les images avec SVGO et Imagemin pour la performance globale. Combinez ces briques pour une page d’accueil de freelance moderne, rapide et lisible sur mobile.

Étape 10 : tester sur appareils réels et navigateurs cibles

Avant de publier, testez sur Chrome Android, Safari iOS et Firefox desktop au minimum. À Dakar, plus de 70 % du trafic vient de smartphones Android d’entrée de gamme. Sur ces machines, la fluidité de l’animation dépend du nombre de reflows déclenchés par textContent. Vérifiez avec l’onglet Performance de Chrome DevTools que vous restez sous 16 ms par frame.

Si vous notez des saccades, remplacez la concaténation par requestAnimationFrame et regroupez les écritures DOM. Le gain est mesurable sur Tecno Spark ou Itel A57, terminaux dominants au Sénégal et au Burkina Faso. Documentez vos mesures dans un fichier PERF.md du projet, c’est une bonne pratique pour les audits clients.

Étape 11 : déployer et mesurer la conversion

Une fois l’effet publié, branchez Plausible, Matomo ou un dashboard maison pour suivre le scroll-depth et le clic sur le CTA. L’objectif n’est pas l’effet en soi mais la conversion visiteur vers prospect. Comparez deux versions A/B : avec et sans typewriter. Les chiffres décident.

Si l’effet booste réellement le clic d’au moins 8 %, conservez-le. Sinon, gardez la phrase statique : la sobriété l’emporte souvent en B2B. Cette discipline du test évite les décisions purement esthétiques et professionnalise votre pratique de freelance ou d’agence en Afrique francophone.

مشاركة