MutationObserver

Observe les changements dans l’arbre du DOM : éléments ajoutés et supprimés, changements d’attributs et de texte, et les signale dans une seule fonction.

Renvoie
Un nouvel objet MutationObserver.
Sur cette page

Exemple

#
JavaScript
const chat = document.querySelector('#chat')
const unread = document.querySelector('#unread')

const observer = new MutationObserver((mutations) => {
  console.log(`Changements dans ce lot : ${mutations.length}`)
  for (const mutation of mutations) {
    mutation.addedNodes.forEach((node) => console.log('Nouveau message :', node.textContent))
  }
  unread.textContent = chat.children.length - 1
})

observer.observe(chat, { childList: true })

function addMessage(text) {
  const item = document.createElement('li')
  item.textContent = text
  chat.append(item)
}

addMessage('Lucas : Hé !')
addMessage('Emma : Qui fait le quiz ?')
console.log('Code synchrone terminé')
Console
Code synchrone terminé
Changements dans ce lot : 2
Nouveau message : Lucas : Hé !
Nouveau message : Emma : Qui fait le quiz ?

Les deux messages ont été ajoutés l’un après l’autre, donc l’observateur les a reçus en un seul appel, seulement après la fin du code synchrone.

Définition et utilisation

#

MutationObserver permet de réagir aux changements dans la structure de la page, quel que soit le fragment de code qui les a provoqués. C’est utile quand un autre script, une bibliothèque ou une extension modifie le contenu et que vous n’avez pas accès à l’endroit où cela se passe, par exemple pour compter les nouveaux messages d’un chat ou surveiller les attributs d’un widget.

La méthode observe(element, options) exige d’indiquer quoi observer : childList (ajout et suppression d’enfants), attributes (changements d’attributs) ou characterData (changements de texte dans les nœuds de texte). L’option subtree étend l’observation à tous les descendants, attributeFilter la restreint à certains attributs, et attributeOldValue mémorise leurs valeurs précédentes.

La fonction de rappel reçoit un tableau d’objets MutationRecord avec des champs comme type, target, addedNodes, removedNodes et attributeName. Les changements sont regroupés et transmis ensemble comme une microtâche, c’est-à-dire après la fin du code en cours, mais avant les timers. Plusieurs changements effectués l’un après l’autre arrivent donc dans un seul appel.

La méthode disconnect() met fin à l’observation, et takeRecords() renvoie les changements déjà collectés mais pas encore transmis. Évitez d’observer tout le document avec subtree: true si ce n’est pas nécessaire, car lors de grands changements la fonction de rappel serait appelée très souvent.

Syntaxe

#
Syntaxe
new MutationObserver(callback)

observer.observe(target, options)
observer.disconnect()
observer.takeRecords()

Paramètres

#
  • callback

    function (mutations, observer)

    La fonction appelée après des changements. Elle reçoit un tableau d’objets MutationRecord.
  • target

    nœud

    L’élément (ou un autre nœud) à partir duquel commence l’observation.
  • options

    objet

    Au moins une des options childList, attributes ou characterData réglée sur true, plus éventuellement subtree, attributeFilter, attributeOldValue et characterDataOldValue.

Plus d’exemples

#
Changement d’attribut avec la valeur précédente
JavaScript
const lesson = document.querySelector('#lesson')

const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    console.log(`${mutation.attributeName}, valeur précédente : ${mutation.oldValue}`)
  }
  console.log('Statut actuel :', lesson.dataset.status)
})

observer.observe(lesson, { attributeFilter: ['data-status'], attributeOldValue: true })

lesson.dataset.status = 'in-progress'
lesson.dataset.status = 'done'
lesson.title = 'Terminée'
Console
data-status, valeur précédente : todo
data-status, valeur précédente : in-progress
Statut actuel : done

Le changement de title n’atteint pas l’observateur, car attributeFilter limite l’observation à data-status. Chaque changement a sa propre entrée avec la valeur d’avant le changement.

Un sous-arbre entier et disconnect()
JavaScript
const editor = document.querySelector('#editor')
const note = document.querySelector('#note')

const observer = new MutationObserver((mutations) => {
  console.log(mutations.map((mutation) => mutation.type))
})

observer.observe(editor, { childList: true, characterData: true, subtree: true })

note.firstChild.data = 'Note sur les boucles'
editor.append(document.createElement('hr'))

setTimeout(() => {
  observer.disconnect()
  note.firstChild.data = 'Personne ne verra ce changement'
  console.log('Observation terminée')
}, 100)
Console
['characterData', 'childList']
Observation terminée

Un changement de texte dans un nœud de texte est characterData, et l’ajout de <hr> est childList. Le texte se trouve plus profondément que #editor, c’est pourquoi l’option subtree est nécessaire. Après disconnect(), les changements suivants ne sont plus signalés.

Compatibilité des navigateurs

#

Largement disponible

Fonctionne dans tous les navigateurs modernes, y compris sur mobile. Vous pouvez l’utiliser sans crainte.

  • Chrome
  • Edge
  • Firefox
  • Safari

Bonnes pratiques

#
  • Indiquez toujours dans les options ce qu’il faut observer. observe(element) seul, sans childList, attributes ni characterData, lève une erreur TypeError.
  • Ne modifiez pas le fragment observé dans la fonction de rappel sans condition. Chaque changement de ce type redéclenche l’observateur, et une boucle infinie arrive vite.
  • Si vous modifiez vous-même le DOM dans votre code, il est plus simple d’appeler la fonction correspondante juste après le changement. Un observateur est utile quand les changements viennent d’un code que vous ne contrôlez pas.

Entrées associées

#

Vous voyez une erreur ou il manque un exemple ? Écrivez-nous.