W3docs

JavaScript MutationObserver API

Impara la JavaScript MutationObserver API: osserva i cambiamenti del DOM, i metodi observe/disconnect/takeRecords, i MutationRecord e pattern pratici.

L'API MutationObserver in JavaScript ti permette di monitorare una parte del DOM ed eseguire una callback ogni volta che qualcosa al suo interno cambia — un nodo viene aggiunto o rimosso, un attributo viene modificato o il contenuto testuale viene aggiornato. A differenza degli obsoleti mutation events che ha sostituito, MutationObserver raggruppa le modifiche e le consegna in modo asincrono, quindi non blocca la pagina né lancia un evento separato per ogni piccola modifica.

Questa guida tratta cosa puoi osservare, i tre metodi esposti da ogni observer, la struttura di un MutationRecord, i casi d'uso nel mondo reale, le insidie più comuni e un esempio interattivo che puoi eseguire.

Quando useresti un MutationObserver?

Si ricorre a un MutationObserver quando contenuto che non controlli cambia e hai bisogno di reagire:

  • Contenuto di terze parti / iniettato — un widget, un annuncio pubblicitario o un blocco renderizzato da un CMS appare nel DOM e hai bisogno di stilizzarlo o potenziarlo.
  • Rilevare quando un elemento esiste finalmente — attendere un nodo che un framework renderizza in seguito, invece di fare polling con setInterval.
  • Sincronizzare l'interfaccia ai cambiamenti degli attributi — reagire quando class, style, disabled o un attributo data-* cambia su un elemento a cui non puoi aggiungere un listener.
  • Ridimensionamento automatico o ricalcolo — ricalcolare il layout quando dei figli vengono inseriti in un contenitore.
  • Mantenere un modello sincronizzato con il testo di un contenteditable.

Se hai solo bisogno di sapere quando un elemento entra o esce dal viewport, usa invece un IntersectionObserver — è progettato appositamente e più economico per questo scopo.

I tre metodi

Ogni istanza di observer espone esattamente tre metodi:

MetodoCosa fa
observe(target, options)Inizia a monitorare target usando un object di opzioni. Chiamalo di nuovo con un target diverso per osservare più di un nodo con lo stesso observer.
disconnect()Smette di monitorare tutti i target. La callback non verrà più chiamata. Chiamalo sempre quando hai finito per evitare memory leak.
takeRecords()Restituisce in modo sincrono (e cancella) tutti i MutationRecord in sospeso che non sono stati ancora consegnati alla callback. Utile appena prima di disconnect() per non perdere l'ultimo batch.

L'object options passato a observe() deve abilitare almeno uno tra childList, attributes o characterData, altrimenti la chiamata genera un TypeError.

OpzioneSignificato
childListMonitora l'aggiunta/rimozione di nodi figli diretti.
attributesMonitora i cambiamenti degli attributi.
characterDataMonitora i cambiamenti ai dati dei nodi di testo.
subtreeEstende quanto sopra a tutti i discendenti, non solo al target.
attributeOldValueRegistra il valore precedente dell'attributo (implica attributes).
characterDataOldValueRegistra il valore testuale precedente (implica characterData).
attributeFilterArray di nomi di attributi da monitorare — ignora gli altri.

Esempio di Mutation Observer

Ecco un esempio di base per capire come funziona un Mutation Observer, mostrando visivamente i cambiamenti nel 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>

Questo esempio mostra come usare un Mutation Observer per rilevare e rispondere alle modifiche nel Document Object Model (DOM) di una pagina web. Ecco cosa fa ogni parte del JavaScript e cosa puoi aspettarti di vedere quando interagisci con l'esempio:

  1. Configurazione del Mutation Observer:
    • Nodo target: è l'elemento DOM che vuoi monitorare. In questo caso, è il div con ID target.
    • Configurazioni: specificano quali tipi di cambiamenti vuoi monitorare:
      • attributes: l'observer rileverà i cambiamenti agli attributi (come style o class).
      • childList: verificherà l'aggiunta o la rimozione di elementi figli (come nuovi div aggiunti).
      • subtree: assicura che l'observer controlli non solo l'elemento target, ma anche i suoi discendenti. Nota che subtree ha effetto solo se è abilitato anche childList o attributes.
      • attributeOldValue: registra il valore precedente di qualsiasi attributo che viene modificato (utile per tracciare i cambiamenti).
  2. Definizione di una Callback:
    • Questa funzione viene eseguita ogni volta che l'observer rileva un cambiamento in base alle configurazioni impostate.
    • Itera su tutte le mutazioni rilevate e crea un messaggio di log per ognuna:
      • Se un elemento figlio viene aggiunto o rimosso, registra "A child node has been added or removed." in verde.
      • Se un attributo viene modificato (come il colore di sfondo), registra "The mutation.attributeName attribute was modified." in blu.
  3. Istanza dell'Observer:
    • Il Mutation Observer viene creato e collegato alla funzione di callback.
  4. Avvio dell'osservazione:
    • L'observer inizia a monitorare il div target per qualsiasi cambiamento specificato nelle configurazioni.
  5. Funzioni interattive:
    • Aggiungi nuovo elemento: attivata dal clic su un pulsante, questa funzione aggiunge un nuovo div con il testo "New element added!" all'interno del div target.
    • Cambia attributo: anch'essa attivata dallo stesso clic del pulsante, questa funzione alterna il colore di sfondo del div target tra 'lightgray' e 'lightblue'. Nota: mentre l'onclick inline funziona per questo esempio, l'uso di addEventListener è consigliato per una migliore separazione del codice in produzione.

Risultati attesi:

  • Aggiunta di un nuovo elemento:
    • Ogni volta che fai clic sul pulsante, viene aggiunto un nuovo div. Questo attiva il controllo childList dell'observer e vedrai un messaggio verde con "A child node has been added or removed."
  • Modifica di un attributo:
    • Lo stesso clic del pulsante cambierà il colore di sfondo del div target. Questo attiva il controllo degli attributi dell'observer. Vedrai un messaggio blu che indica quale attributo è stato modificato ("The style attribute was modified.").

Questo esempio mostra efficacemente come i Mutation Observer possano essere utilizzati per monitorare e registrare le modifiche nel DOM, fornendo un feedback in tempo reale su ciò che accade nella pagina web.

Leggere un MutationRecord

La callback riceve un array di object MutationRecord — uno per ogni cambiamento rilevato. Le proprietà più utili sono:

  • type"childList", "attributes" o "characterData".
  • target — il nodo interessato dalla mutazione.
  • addedNodes / removedNodesNodeList di nodi inseriti/rimossi (per childList).
  • attributeName — l'attributo modificato (per attributes).
  • oldValue — il valore precedente, ma solo se era abilitato attributeOldValue o characterDataOldValue.
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 pattern pratico: attendere che un elemento appaia

Un uso comune nel mondo reale è risolvere una Promise nel momento in cui un nodo compare nel DOM — molto meglio del polling. L'observer si disconnette non appena trova l'elemento:

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");

Questo si abbina naturalmente con async/await e le Promise.

Insidie e best practice

  • La callback è asincrona e in batch. Le mutazioni vengono accodate come microtask e consegnate dopo che lo script corrente è terminato — vedi microtask e l'event loop. Non riceverai un record per ogni singola modifica; li ricevi raggruppati.
  • La callback viene eseguita dopo il cambiamento. Vieni notificato di ciò che è già accaduto — non puoi annullare o prevenire una mutazione come faresti con preventDefault() su un evento.
  • oldValue richiede opt-in. Se leggi record.oldValue senza aver abilitato attributeOldValue/characterDataOldValue, il valore è null.
  • Evita di mutare il sottoalbero osservato all'interno della callback a meno che non sia intenzionale — farlo può generare altri record e creare un ciclo di feedback.
  • Chiama sempre disconnect(). Un observer attivo mantiene il suo target raggiungibile, quindi dimenticarsi di disconnettersi causa memory leak. Disconnetti quando il componente viene smontato o il lavoro è terminato.
  • Chiama takeRecords() prima di disconnetterti se l'ultimo batch è importante — disconnect() scarta i record non ancora consegnati.

Conclusione

I Mutation Observer sono una parte fondamentale del toolkit JavaScript, che offre soluzioni dinamiche per gestire in modo efficiente le modifiche al DOM. Permettono agli sviluppatori di costruire applicazioni web reattive e interattive che rispondono senza problemi alle interazioni degli utenti e alle modifiche programmatiche del DOM. Pur essendo potenti, è essenziale utilizzare i Mutation Observer con giudizio per mantenere prestazioni ottimali e una buona esperienza utente. Selezionando attentamente quali mutazioni osservare, riducendo al minimo l'overhead nelle callback e chiamando observer.disconnect() quando l'observer non è più necessario per prevenire memory leak, gli sviluppatori possono sfruttare i Mutation Observer per migliorare le funzionalità del sito senza compromettere l'efficienza. Comprendere e applicare questi principi permette di creare interfacce web avanzate e intuitive che si distinguono nel panorama digitale moderno.

Pratica

Pratica
Quali delle seguenti affermazioni sono vere riguardo al JavaScript Mutation Observer?
Quali delle seguenti affermazioni sono vere riguardo al JavaScript Mutation Observer?
Was this page helpful?