API JavaScript MutationObserver
Apprenez l'API JavaScript MutationObserver : observer les changements DOM, les méthodes observe/disconnect/takeRecords, les MutationRecords et des exemples pratiques.
L'API MutationObserver en JavaScript vous permet d'observer une partie du DOM et d'exécuter un callback dès que quelque chose y change — un nœud est ajouté ou supprimé, un attribut est modifié, ou le contenu textuel est mis à jour. Contrairement aux obsolètes mutation events qu'elle remplace, MutationObserver regroupe les changements et les livre de manière asynchrone, sans bloquer la page ni déclencher un événement distinct pour chaque petite modification.
Ce guide couvre ce que vous pouvez observer, les trois méthodes exposées par chaque observer, la structure d'un MutationRecord, des cas d'usage concrets, les pièges courants et un exemple interactif que vous pouvez exécuter.
Quand utiliser un MutationObserver ?
On fait appel à un MutationObserver lorsqu'un contenu que l'on ne contrôle pas change et qu'il faut y réagir :
- Contenu tiers / injecté — un widget, une publicité ou un bloc rendu par un CMS apparaît dans le DOM et vous devez le styler ou l'enrichir.
- Détecter quand un élément existe enfin — attendre qu'un nœud rendu plus tard par un framework soit disponible, plutôt que de sonder avec
setInterval. - Synchroniser l'UI avec les changements d'attributs — réagir quand
class,style,disabledou un attributdata-*change sur un élément auquel vous ne pouvez pas ajouter d'écouteur. - Redimensionnement ou recalcul automatique — recalculer la mise en page lorsque des enfants sont insérés dans un conteneur.
- Maintenir un modèle synchronisé avec le texte d'un
contenteditable.
Si vous avez seulement besoin de savoir quand un élément entre ou sort du viewport, utilisez plutôt un IntersectionObserver — il est conçu à cet effet et moins coûteux pour ce travail.
Les trois méthodes
Chaque instance d'observer expose exactement trois méthodes :
| Méthode | Ce qu'elle fait |
|---|---|
observe(target, options) | Commence à surveiller target en utilisant un objet d'options. Appelez-la à nouveau avec une cible différente pour surveiller plusieurs nœuds avec le même observer. |
disconnect() | Arrête de surveiller toutes les cibles. Le callback ne se déclenchera plus. Appelez toujours cette méthode lorsque vous avez terminé pour éviter les fuites mémoire. |
takeRecords() | Retourne (et vide) de manière synchrone les MutationRecords en attente qui n'ont pas encore été transmis au callback. Utile juste avant disconnect() pour ne pas perdre le dernier lot. |
L'objet options passé à observe() doit activer au moins l'une des options childList, attributes ou characterData, sinon l'appel lève une TypeError.
| Option | Signification |
|---|---|
childList | Surveille l'ajout ou la suppression de nœuds enfants directs. |
attributes | Surveille les changements d'attributs. |
characterData | Surveille les changements dans les données des nœuds texte. |
subtree | Étend la surveillance à tous les descendants, pas seulement à la cible. |
attributeOldValue | Enregistre la valeur précédente de l'attribut (implique attributes). |
characterDataOldValue | Enregistre la valeur textuelle précédente (implique characterData). |
attributeFilter | Array de noms d'attributs à surveiller — ignore les autres. |
Exemple de Mutation Observer
Voici un exemple de base pour illustrer le fonctionnement d'un Mutation Observer en indiquant visuellement les changements dans le DOM.
<!DOCTYPE html>
<html>
<head>
<title>Exploring DOM Changes: Live Examples with Mutation Observers</title>
</head>
<body>
<div id="target" style="background-color: lightgray; padding: 10px;">
Watch this space for changes!
</div>
<button style="margin-top: 10px;" onclick="addNewElement(); changeAttribute();">Add New Element and Change Color</button>
<div id="log" style="margin-top: 20px;"></div>
<script>
// Get the element to observe
const targetNode = document.getElementById('target');
// Define configurations for the observer
const config = { attributes: true, childList: true, subtree: true, attributeOldValue: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
for (const mutation of mutationsList) {
const message = document.createElement('p');
if (mutation.type === 'childList') {
message.textContent = 'A child node has been added or removed.';
message.style.color = 'green';
} else if (mutation.type === 'attributes') {
message.textContent = 'The ' + mutation.attributeName + ' attribute was modified.';
message.style.color = 'blue';
}
document.getElementById('log').appendChild(message);
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Function to add new elements
function addNewElement() {
const newElement = document.createElement('div');
newElement.textContent = 'New element added!';
targetNode.appendChild(newElement);
}
// Function to change attributes
function changeAttribute() {
const currentColor = targetNode.style.backgroundColor;
targetNode.style.backgroundColor = currentColor === 'lightgray' ? 'lightblue' : 'lightgray';
}
</script>
</body>
</html>Cet exemple illustre comment utiliser un Mutation Observer pour détecter et réagir aux changements dans le Document Object Model (DOM) d'une page web. Voici ce que fait chaque partie du JavaScript et ce que vous pouvez observer en interagissant avec l'exemple :
- Mise en place du Mutation Observer :
- Nœud cible : c'est l'élément DOM que vous souhaitez surveiller. Ici, il s'agit du
divavec l'IDtarget. - Configurations : elles précisent les types de changements à surveiller :
attributes: l'observer détectera les changements d'attributs (comme le style ou la classe).childList: il vérifiera l'ajout ou la suppression d'éléments enfants (comme de nouveauxdivajoutés).subtree: l'observer inspecte non seulement l'élément cible, mais aussi ses descendants. Notez quesubtreen'a d'effet que sichildListouattributesest également activé.attributeOldValue: enregistre la valeur précédente de tout attribut modifié (utile pour suivre les changements).
- Nœud cible : c'est l'élément DOM que vous souhaitez surveiller. Ici, il s'agit du
- Définir une fonction callback :
- Cette fonction s'exécute chaque fois que l'observer détecte un changement selon les configurations définies.
- Elle parcourt toutes les mutations détectées et crée un message de journal pour chacune :
- Si un élément enfant est ajouté ou supprimé, elle consigne "A child node has been added or removed." en vert.
- Si un attribut est modifié (comme la couleur de fond), elle consigne "The
mutation.attributeNameattribute was modified." en bleu.
- Instance de l'observer :
- Le Mutation Observer est créé et lié à la fonction callback.
- Démarrer l'observation :
- L'observer commence à surveiller le
divtargetpour tout changement spécifié dans les configurations.
- L'observer commence à surveiller le
- Fonctions interactives :
- Ajouter un nouvel élément : déclenchée par un clic sur le bouton, cette fonction ajoute un nouveau
divavec le texte "New element added!" dans ledivtarget. - Modifier un attribut : également déclenchée par le même clic, cette fonction alterne la couleur de fond du
divtargetentre 'lightgray' et 'lightblue'. Remarque : bien que leonclicken ligne fonctionne pour cet exemple, il est recommandé d'utiliseraddEventListenerpour une meilleure séparation du code en production.
- Ajouter un nouvel élément : déclenchée par un clic sur le bouton, cette fonction ajoute un nouveau
Résultats attendus :
- Ajout d'un nouvel élément :
- Chaque clic sur le bouton ajoute un nouveau
div. Cela déclenche la vérificationchildListde l'observer et un message vert "A child node has been added or removed." apparaît.
- Chaque clic sur le bouton ajoute un nouveau
- Modification d'un attribut :
- Le même clic change la couleur de fond du
divtarget. Cela déclenche la vérification d'attribut de l'observer. Un message bleu indique quel attribut a été modifié ("The style attribute was modified.").
- Le même clic change la couleur de fond du
Cet exemple démontre efficacement comment les Mutation Observers peuvent être utilisés pour surveiller et journaliser les changements dans le DOM, en fournissant un retour en temps réel sur ce qui se passe dans la page web.
Lire un MutationRecord
Le callback reçoit un array d'objets MutationRecord — un par changement détecté. Les propriétés les plus utiles sont :
type—"childList","attributes"ou"characterData".target— le nœud affecté par la mutation.addedNodes/removedNodes— desNodeLists de nœuds insérés/supprimés (pourchildList).attributeName— l'attribut modifié (pourattributes).oldValue— la valeur précédente, mais uniquement siattributeOldValueoucharacterDataOldValueétait activé.
const observer = new MutationObserver((records) => {
for (const record of records) {
if (record.type === "attributes") {
console.log(`${record.attributeName} changed from "${record.oldValue}"`);
} else if (record.type === "childList") {
console.log(`+${record.addedNodes.length} / -${record.removedNodes.length} nodes`);
}
}
});Un modèle pratique : attendre qu'un élément apparaisse
Un usage courant en situation réelle consiste à résoudre une Promise au moment précis où un nœud apparaît dans le DOM — bien plus efficace que le sondage. L'observer se déconnecte dès qu'il trouve l'élément :
function waitForElement(selector) {
return new Promise((resolve) => {
const existing = document.querySelector(selector);
if (existing) return resolve(existing);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect(); // stop watching once found
resolve(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
});
}
// Usage with async/await:
// const card = await waitForElement(".lazy-card");Ce modèle s'associe naturellement avec async/await et les Promises.
Pièges et bonnes pratiques
- Le callback est asynchrone et groupé. Les mutations sont mises en file d'attente en tant que microtasks et livrées après la fin du script en cours — voir microtasks et la boucle d'événements. Vous ne recevez pas un enregistrement par changement individuel ; ils vous parviennent regroupés.
- Votre callback s'exécute après le changement. Vous êtes notifié de ce qui s'est déjà produit — vous ne pouvez pas annuler ni empêcher une mutation comme vous pourriez le faire avec
preventDefault()sur un événement. oldValueest opt-in. Si vous lisezrecord.oldValuesans avoir activéattributeOldValue/characterDataOldValue, la valeur estnull.- Évitez de muter le sous-arbre observé dans le callback sauf intentionnellement — cela peut déclencher davantage d'enregistrements et créer une boucle de rétroaction.
- Appelez toujours
disconnect(). Un observer actif maintient sa cible accessible, et oublier de le déconnecter provoque des fuites mémoire. Déconnectez-le lorsque le composant se démonte ou que le travail est terminé. - Appelez
takeRecords()avant de déconnecter si le dernier lot est important —disconnect()supprime les enregistrements non livrés.
Conclusion
Les Mutation Observers sont un élément essentiel de la boîte à outils JavaScript, offrant des solutions dynamiques pour gérer efficacement les changements du DOM. Ils permettent aux développeurs de créer des applications web réactives et interactives qui répondent harmonieusement aux interactions des utilisateurs et aux modifications programmatiques du DOM. Bien qu'ils soient puissants, il est essentiel d'utiliser les Mutation Observers avec discernement pour maintenir des performances optimales et une bonne expérience utilisateur. En sélectionnant soigneusement les mutations à observer, en minimisant la charge dans les callbacks de mutation et en appelant observer.disconnect() lorsque l'observer n'est plus nécessaire pour prévenir les fuites mémoire, les développeurs peuvent tirer parti des Mutation Observers pour améliorer les fonctionnalités du site sans compromettre l'efficacité. Comprendre et appliquer ces principes permet de créer des interfaces web avancées et conviviales qui se distinguent dans le paysage numérique moderne.