Monitorare il DOM in tempo reale con MutationObserver

by Anastasia P.
SHARE
Monitorare il DOM in tempo reale con MutationObserver
© Guida-HTML5.it

Introduzione

Quando un’applicazione JavaScript aggiorna la pagina, gli elementi del DOM possono essere aggiunti, rimossi o modificati in qualsiasi momento. In molti casi non basta eseguire una funzione una sola volta al caricamento: è necessario accorgersi automaticamente dei cambiamenti e reagire di conseguenza.

Per questo scopo il browser mette a disposizione l’API MutationObserver. Un observer permette di controllare un elemento del DOM e ricevere una notifica quando cambiano i suoi nodi figli, il testo contenuto oppure i suoi attributi.

Un esempio concreto è una lista di notifiche aggiornata da codice esterno. Ogni nuova notifica dovrebbe ricevere automaticamente un comportamento, come un pulsante per archiviarla, anche se non esisteva quando è stato eseguito lo script iniziale.

MutationObserver è utile perché evita controlli ripetitivi con setInterval e reagisce soltanto quando si verifica una modifica reale.

Codice completo

<section id="pannello-notifiche">
  <h2>Notifiche</h2>
  <ul id="lista-notifiche"></ul>
  <p id="stato-lista">Nessuna notifica presente.</p>
</section>

<button id="aggiungi-notifica" type="button">
  Aggiungi notifica
</button>

<script>
  const lista = document.querySelector("#lista-notifiche");
  const statoLista = document.querySelector("#stato-lista");
  const pulsanteAggiunta = document.querySelector("#aggiungi-notifica");

  // Aggiorna il messaggio mostrato quando la lista è vuota
  function aggiornaStatoLista() {
    const numeroNotifiche = lista.children.length;

    statoLista.hidden = numeroNotifiche > 0;

    if (numeroNotifiche > 0) {
      statoLista.textContent =
        `Sono presenti ${numeroNotifiche} notifiche.`;
    } else {
      statoLista.textContent = "Nessuna notifica presente.";
    }
  }

  // Aggiunge un´azione agli elementi che rappresentano notifiche
  function preparaNotifica(elemento) {
    const pulsante = elemento.querySelector(".archivia");

    if (!pulsante) {
      return;
    }

    pulsante.addEventListener("click", () => {
      elemento.remove();
    });
  }

  // Configura l´observer per osservare aggiunte e rimozioni nella lista
  const observer = new MutationObserver((mutazioni) => {
    for (const mutazione of mutazioni) {
      if (mutazione.type === "childList") {
        for (const nodo of mutazione.addedNodes) {
          // I nodi di testo non sono elementi HTML
          if (nodo.nodeType === Node.ELEMENT_NODE) {
            preparaNotifica(nodo);
          }
        }
      }
    }

    aggiornaStatoLista();
  });

  observer.observe(lista, {
    childList: true,
    subtree: false
  });

  // Anche questo elemento viene aggiunto dopo l´avvio dell´observer
  pulsanteAggiunta.addEventListener("click", () => {
    const numero = lista.children.length + 1;

    const nuovaNotifica = document.createElement("li");
    nuovaNotifica.className = "notifica";
    nuovaNotifica.innerHTML = `
      Nuova notifica numero ${numero}
      <button class="archivia" type="button">Archivia</button>
    `;

    lista.append(nuovaNotifica);
  });

  aggiornaStatoLista();

  // Per interrompere il monitoraggio:
  // observer.disconnect();
</script>

Spiegazione

Creare un osservatore

Un osservatore viene creato passando a MutationObserver una funzione di callback. Questa funzione viene eseguita quando il browser rileva una o più modifiche nel DOM osservato.

const observer = new MutationObserver((mutazioni) => {
  console.log("Il DOM è cambiato:", mutazioni);
});

Il parametro mutazioni è un array di oggetti MutationRecord. Ogni record descrive un cambiamento e contiene informazioni come il tipo di modifica, i nodi aggiunti e quelli rimossi.

Configurare observe

Per iniziare il monitoraggio si usa il metodo observe(). Il primo argomento è l’elemento da controllare, mentre il secondo specifica i tipi di cambiamento da osservare.

observer.observe(elemento, {
  childList: true,
  subtree: true,
  attributes: true,
  characterData: true
});
  • childList: osserva l’aggiunta o la rimozione di nodi figli.
  • subtree: estende l’osservazione anche agli elementi discendenti.
  • attributes: rileva modifiche agli attributi HTML.
  • characterData: rileva modifiche al contenuto testuale dei nodi.

È consigliabile attivare soltanto le opzioni necessarie. Un’osservazione troppo ampia può produrre molte callback e rendere più difficile capire l’origine delle modifiche.

Distinguere gli elementi dai nodi di testo

La proprietà addedNodes può contenere elementi HTML, ma anche nodi di testo o altri tipi di nodo. Prima di usare metodi come querySelector, è quindi importante verificare nodeType.

if (nodo.nodeType === Node.ELEMENT_NODE) {
  console.log("È un elemento HTML:", nodo);
}

Fermare l’osservazione

Quando un componente viene rimosso o non deve più essere controllato, è buona pratica chiamare disconnect(). In questo modo si evita di mantenere inutilmente riferimenti e callback attive.

observer.disconnect();

Best practice

  • Osserva il contenitore più specifico possibile, invece dell’intero documento.
  • Attiva solo le opzioni necessarie, come childList o attributes.
  • Controlla sempre il tipo dei nodi presenti in addedNodes e removedNodes.
  • Evita di modificare continuamente lo stesso elemento osservato senza una condizione di sicurezza: potresti creare un ciclo di mutazioni.
  • Per liste dinamiche, valuta anche la delegazione degli eventi quando l’azione può essere gestita dal contenitore.
  • Chiama disconnect() quando il componente non è più in uso.
  • Se la callback deve eseguire operazioni costose, raggruppa le modifiche e limita il lavoro svolto per ogni ciclo.
  • Preferisci textContent per testo proveniente da utenti o fonti esterne. Usa innerHTML soltanto quando il markup è controllato e sicuro.

Riepilogo

MutationObserver permette di reagire alle modifiche del DOM senza eseguire controlli continui. La procedura essenziale consiste nel selezionare un elemento, creare un observer, configurare le mutazioni da monitorare e avviare l’osservazione con observe().

Nel caso della lista di notifiche, ogni nuovo elemento viene individuato automaticamente, preparato con il proprio pulsante e incluso nel conteggio aggiornato. Questo approccio è particolarmente utile per interfacce composte da componenti dinamici, contenuti caricati in modo asincrono e aggiornamenti prodotti da librerie esterne.

Approfondisci con risorse ufficiali

SHARE