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,disabledo un attributodata-*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:
| Metodo | Cosa 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.
| Opzione | Significato |
|---|---|
childList | Monitora l'aggiunta/rimozione di nodi figli diretti. |
attributes | Monitora i cambiamenti degli attributi. |
characterData | Monitora i cambiamenti ai dati dei nodi di testo. |
subtree | Estende quanto sopra a tutti i discendenti, non solo al target. |
attributeOldValue | Registra il valore precedente dell'attributo (implica attributes). |
characterDataOldValue | Registra il valore testuale precedente (implica characterData). |
attributeFilter | Array 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:
- Configurazione del Mutation Observer:
- Nodo target: è l'elemento DOM che vuoi monitorare. In questo caso, è il
divcon IDtarget. - 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 chesubtreeha effetto solo se è abilitato anchechildListoattributes.attributeOldValue: registra il valore precedente di qualsiasi attributo che viene modificato (utile per tracciare i cambiamenti).
- Nodo target: è l'elemento DOM che vuoi monitorare. In questo caso, è il
- 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.attributeNameattribute was modified." in blu.
- Istanza dell'Observer:
- Il Mutation Observer viene creato e collegato alla funzione di callback.
- Avvio dell'osservazione:
- L'observer inizia a monitorare il
divtargetper qualsiasi cambiamento specificato nelle configurazioni.
- L'observer inizia a monitorare il
- 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
divtarget. - Cambia attributo: anch'essa attivata dallo stesso clic del pulsante, questa funzione alterna il colore di sfondo del
divtargettra 'lightgray' e 'lightblue'. Nota: mentre l'onclickinline funziona per questo esempio, l'uso diaddEventListenerè consigliato per una migliore separazione del codice in produzione.
- Aggiungi nuovo elemento: attivata dal clic su un pulsante, questa funzione aggiunge un nuovo div con il testo "New element added!" all'interno del
Risultati attesi:
- Aggiunta di un nuovo elemento:
- Ogni volta che fai clic sul pulsante, viene aggiunto un nuovo div. Questo attiva il controllo
childListdell'observer e vedrai un messaggio verde con "A child node has been added or removed."
- Ogni volta che fai clic sul pulsante, viene aggiunto un nuovo div. Questo attiva il controllo
- Modifica di un attributo:
- Lo stesso clic del pulsante cambierà il colore di sfondo del
divtarget. Questo attiva il controllo degli attributi dell'observer. Vedrai un messaggio blu che indica quale attributo è stato modificato ("The style attribute was modified.").
- Lo stesso clic del pulsante cambierà il colore di sfondo del
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/removedNodes—NodeListdi nodi inseriti/rimossi (perchildList).attributeName— l'attributo modificato (perattributes).oldValue— il valore precedente, ma solo se era abilitatoattributeOldValueocharacterDataOldValue.
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. oldValuerichiede opt-in. Se leggirecord.oldValuesenza aver abilitatoattributeOldValue/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.