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
childListoattributes. - Controlla sempre il tipo dei nodi presenti in
addedNodeseremovedNodes. - 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
textContentper testo proveniente da utenti o fonti esterne. UsainnerHTMLsoltanto 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.
