Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
JavaScript

Optimiser les événements : debounce et throttle

Découvrez comment optimiser les événements qui se déclenchent en rafale (input, scroll, resize, mousemove) grâce aux techniques debounce et throttle. La leçon explique leur fonctionnement, montre comm...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Découvrez comment optimiser les événements qui se déclenchent en rafale (input, scroll, resize, mousemove) grâce aux techniques debounce et throttle. La leçon explique leur fonctionnement, montre comment les écrire en JavaScript, quand choisir l'une ou l'autre, et présente les alternatives modernes : requestAnimationFrame, écouteurs passifs et IntersectionObserver. Avec des exemples mesurés dans la console et des activités.
  1. Objectifs

    • Identifier les événements qui se déclenchent en rafale (input, scroll, resize, mousemove).
    • Comprendre et écrire une fonction debounce.
    • Comprendre et écrire une fonction throttle.
    • Choisir la bonne technique et connaître les alternatives modernes (requestAnimationFrame, écouteurs passifs, IntersectionObserver).
  2. Le problème : les événements en rafale

    • Certains événements se déclenchent des dizaines de fois par seconde:
      • input: à chaque caractère tapé dans un champ ;
      • scroll: en continu pendant le défilement ;
      • resize: pendant tout le redimensionnement de la fenêtre ;
      • mousemove: à chaque déplacement de la souris.
    • Si chaque déclenchement lance un traitement lourd (requête au serveur, calcul, modification du DOM), la page ralentit, le serveur reçoit des requêtes inutiles et la batterie s'use.
    // ✘ Une requête au serveur à CHAQUE touche : « bootstrap » = 9 requêtes
    champRecherche.addEventListener("input", () => {
      rechercher(champRecherche.value);
    });

    Chronologie : événements en rafale, exécutions avec debounce et avec throttle

  3. Debounce : attendre que l'utilisateur s'arrête

    • Le debounce (anti-rebond) retarde l'exécution : la fonction ne s'exécute que lorsque l'événement a cessé depuis un certain délai. Chaque nouvel événement relance le compte à rebours.
    • Image : un ascenseur qui attend que plus personne n'entre pendant 3 secondes avant de fermer ses portes.
    function debounce(fonction, delai = 300) {
      let minuteur;
      return function (...args) {
        clearTimeout(minuteur);                                   // annule le compte précédent
        minuteur = setTimeout(() => fonction.apply(this, args), delai); // relance
      };
    }
    
    // ✔ Une seule recherche, 400 ms après la dernière touche
    const rechercherAvecDelai = debounce((texte) => rechercher(texte), 400);
    champRecherche.addEventListener("input", () => rechercherAvecDelai(champRecherche.value));
    • Comment ça marche : debounce renvoie une nouvelle fonction qui garde en mémoire la variable minuteur (c'est une closure). À chaque appel, l'ancien minuteur est annulé et un nouveau est lancé.
    • Cas d'usage: recherche ou autocomplétion pendant la saisie, validation d'un champ (« ce pseudo est-il libre ? »), sauvegarde automatique d'un brouillon, recalcul de la mise en page à la fin d'un redimensionnement.
  4. Throttle : limiter la fréquence

    • Le throttle (étranglement) exécute la fonction au plus une fois par intervalle de temps, même si l'événement continue.
    • Image : un robinet réglé pour laisser passer une goutte toutes les 200 ms, quelle que soit la pression.
    function throttle(fonction, intervalle = 200) {
      let dernierAppel = 0;
      return function (...args) {
        const maintenant = Date.now();
        if (maintenant - dernierAppel >= intervalle) {
          dernierAppel = maintenant;
          fonction.apply(this, args);
        }
      };
    }
    
    // ✔ La barre de progression de lecture est mise à jour au plus 5 fois par seconde
    const majProgression = throttle(() => {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      barre.style.width = `${(window.scrollY / max) * 100}%`;
    }, 200);
    window.addEventListener("scroll", majProgression);
    • Cas d'usage: suivi du défilement (barre de progression, bouton « remonter »), suivi de la souris, jeux, envoi de statistiques, bouton qu'on ne doit pas pouvoir cliquer 10 fois par seconde.
    • Cette version simple ignore les appels qui arrivent pendant l'intervalle : le dernier état peut donc ne pas être traité. Les versions complètes (par exemple celle de la bibliothèque Lodash) ajoutent un dernier appel en fin de rafale.
  5. Debounce ou throttle ?

  6. DebounceThrottle
    PrincipeAttend la fin de la rafaleExécute régulièrement pendant la rafale
    Nombre d'exécutions pour une rafale de 3 secondes1 (à la fin)Environ 3 s ÷ intervalle
    Question à se poser« Seul le résultat final m'intéresse »« Je veux un retour visuel continu, mais pas trop fréquent »
    ExemplesRecherche, validation, sauvegarde automatiqueDéfilement, souris, jeux, redimensionnement en direct
  1. Les alternatives modernes

    • requestAnimationFrame: pour une mise à jour visuelle (animation, position), on synchronise le travail avec l'affichage de l'écran (environ 60 fois par seconde) :
    let enAttente = false;
    window.addEventListener("scroll", () => {
      if (enAttente) return;
      enAttente = true;
      requestAnimationFrame(() => {
        mettreAJourAffichage();
        enAttente = false;
      });
    });
    • Écouteurs passifs: { passive: true } indique au navigateur que l'écouteur n'appellera pas preventDefault(). Le défilement reste ainsi fluide :
    window.addEventListener("scroll", majProgression, { passive: true });
    • IntersectionObserver: pour savoir si un élément est visible (chargement d'images au défilement, animation à l'apparition), il remplace avantageusement l'écoute de scroll.
    • Annuler les requêtes dépassées: avec une recherche, on peut aussi annuler la requête précédente avec AbortController pour ne jamais afficher un résultat périmé.
  2. Mesurer l'effet

    • Comptez les appels avec console.count() avant et après optimisation.
    • Dans les DevTools, l'onglet Réseau montre le nombre de requêtes envoyées et l'onglet Performance enregistre l'activité de la page pendant quelques secondes.
    champRecherche.addEventListener("input", () => console.count("événement input"));
    const compterRecherche = debounce(() => console.count("recherche lancée"), 400);
    champRecherche.addEventListener("input", compterRecherche);
    // En tapant « bootstrap » d'un trait : input ×9, recherche lancée ×1
  3. Activités

  4. Activité 1 : compter les événements

  • Créez une page avec un champ de texte et une zone très haute (height: 3000px). Comptez avec console.count les événements input, scroll et mousemove pendant 5 secondes d'utilisation.

Activité 2 : recherche avec debounce

  • Créez un tableau de 30 noms de composants Bootstrap. Affichez sous un champ la liste filtrée selon le texte saisi, avec un debounce de 300 ms. Affichez aussi le nombre de filtrages effectués.
  • Comparez avec la version sans debounce en tapant « navbar ».

Activité 3 : barre de progression avec throttle

  • Ajoutez en haut d'un long article une barre de progression Bootstrap (progress) mise à jour au défilement avec un throttle de 100 ms et un écouteur passif.

Activité 4 : choisir la technique

  • Pour chaque situation, choisissez debounce, throttle ou IntersectionObserver et justifiez : (a) vérifier qu'un e-mail n'est pas déjà utilisé ; (b) afficher la position de la souris ; (c) charger les images quand elles approchent de l'écran ; (d) recalculer une grille de cartes quand la fenêtre est redimensionnée ; (e) empêcher un double clic sur « Payer ».
  • Déposez vos fichiers dans le dépôt GitHub js-debounce-throttle.
Corrigé de l'activité 4. (a) debounce : on vérifie une fois la saisie terminée. (b) throttle (ou requestAnimationFrame) : retour visuel régulier. (c) IntersectionObserver. (d) debounce : seul l'état final compte. (e) throttle avec un long intervalle (ou désactiver le bouton jusqu'à la réponse du serveur, ce qui est encore plus sûr).
  • À retenir

    • input, scroll, resize et mousemove se déclenchent en rafale.
    • Debounce: une seule exécution, après la fin de la rafale.
    • Throttle: au plus une exécution par intervalle, pendant la rafale.
    • Pour le visuel, pensez à requestAnimationFrame, aux écouteurs passifs et à IntersectionObserver.

    Sources : MDN Web Docs (developer.mozilla.org), OWASP (owasp.org), documentations d'ESLint, Prettier et Vitest.