10
Events & Listeners
Bubbling, Delegation & Performance
Les événements sont le cœur de l'interactivité web. Comprendre le flow d'événements (bubbling/capturing) et les patterns d'optimisation (delegation, debounce) est essentiel.
🎯 Types d'Événements
Mouse Events
click, dblclickmouseenter, mouseleavemousemove, mousedown
Keyboard Events
keydown, keyupkeypress (deprecated)Accès :
e.key, e.code
Form Events
submit, inputchange, focusblur
Window Events
load, DOMContentLoadedresize, scrollbeforeunload
Touch Events
touchstart, touchmovetouchend, touchcancelMobile/tablet
Custom Events
new CustomEvent()dispatchEvent()Communication inter-composants
🌊 Event Flow : Bubbling vs Capturing
Quand un événement se déclenche, il traverse le DOM en 3 phases : Capturing → Target → Bubbling.
🎬 Visualiseur de Bubbling
Cliquez sur l'enfant (vert) pour voir l'événement "buller" vers le parent.
🌍
Container
📦 Parent
🎯 Enfant (Cliquez-moi !)
Cliquez sur l'enfant pour voir le flow
🛑 preventDefault vs stopPropagation
// preventDefault() : Empêche l'action par défaut
form.addEventListener('submit', (e) => {
e.preventDefault(); // Empêche le rechargement de page
// Traiter le formulaire en JS
});
link.addEventListener('click', (e) => {
e.preventDefault(); // Empêche la navigation
// Custom behavior
});
// stopPropagation() : Arrête la propagation (bubbling/capturing)
child.addEventListener('click', (e) => {
e.stopPropagation(); // Parent ne recevra PAS l'événement
console.log('Only child handler runs');
});
// stopImmediatePropagation() : Arrête TOUS les autres handlers
el.addEventListener('click', (e) => {
e.stopImmediatePropagation();
// Les autres listeners sur 'el' ne s'exécuteront pas
});
🎯 Event Delegation (Pattern Essentiel)
Au lieu d'attacher un listener à chaque élément, attachez-le au parent et utilisez e.target.
// ❌ Mauvais : 1000 listeners
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ Bon : 1 seul listener (delegation)
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e);
}
});
// Avantages :
// - Performance (1 listener au lieu de N)
// - Fonctionne pour les éléments ajoutés dynamiquement
// - Moins de mémoire
⏱️ Debounce & Throttle
Optimisez les événements fréquents (scroll, resize, input) pour éviter de surcharger le navigateur.
🧪 Comparaison Interactive
Tapez dans les inputs pour voir la différence.
❌ Sans optimisation
0
Appels de fonction
✅ Avec Debounce (300ms)
0
Appels de fonction
Explication :
Sans optimisation : La fonction s'exécute à chaque frappe (peut être 100+ fois/seconde).
Avec Debounce : La fonction attend 300ms d'inactivité avant de s'exécuter (1 seul appel après avoir fini de taper).
Sans optimisation : La fonction s'exécute à chaque frappe (peut être 100+ fois/seconde).
Avec Debounce : La fonction attend 300ms d'inactivité avant de s'exécuter (1 seul appel après avoir fini de taper).
// Debounce : Attend la fin de l'activité
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
// Usage : Search autocomplete
const search = debounce((query) => {
fetch(`/api/search?q=${query}`);
}, 300);
input.addEventListener('input', (e) => search(e.target.value));
// Throttle : Limite la fréquence d'exécution
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
}
};
}
// Usage : Scroll tracking
const trackScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 200);
window.addEventListener('scroll', trackScroll);
Best Practice : Utilisez debounce pour les inputs/search (attend la
fin). Utilisez throttle pour scroll/resize (limite la fréquence). Privilégiez
event delegation pour les listes dynamiques.