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, dblclick
mouseenter, mouseleave
mousemove, mousedown
Keyboard Events
keydown, keyup
keypress (deprecated)
Accès : e.key, e.code
Form Events
submit, input
change, focus
blur
Window Events
load, DOMContentLoaded
resize, scroll
beforeunload
Touch Events
touchstart, touchmove
touchend, touchcancel
Mobile/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).
// 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.
← DOM Suivant: Asynchrone →