MutationObserver detects when the DOM changes — elements added, removed, text modified, or attributes changed. Instead of polling the DOM repeatedly, use MutationObserver to react immediately to changes. Perfect for tracking dynamic content, monitoring third-party scripts, and handling dynamically inserted elements.
The old way: polling the DOM
| // ❌ Polling: inefficient, wasted resources setInterval(() => { const newElements = document.querySelectorAll(".dynamic-item:not(.processed)"); newElements.forEach(el => { processElement(el); el.classList.add("processed"); }); }, 500); ← checks every 500ms, even if nothing changed // Problems: // - Wastes CPU checking frequently // - Delays: changes not detected immediately // - Complex to implement correctly |
MutationObserver — the efficient way
| // ✅ MutationObserver: react immediately to changes const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === "childList") { // Element added or removed mutation.addedNodes.forEach(node => { if (node.classList?.contains("dynamic-item")) { processElement(node); } }); } if (mutation.type === "attributes") { // Attribute changed console.log(`${mutation.attributeName} changed`); } }); }); const config = { childList: true, ← watch for added/removed elements subtree: true, ← watch all descendants attributes: true ← watch attribute changes }; observer.observe(document.body, config); // Later: stop observing observer.disconnect(); |
Real-world examples
Detecting dynamically added content
| // Example: JavaScript framework adds elements dynamically const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { ← element node console.log("New element:", node.tagName); initializeElement(node); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); |
Monitoring text content changes
| const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === "characterData") { console.log("Text changed:", mutation.target.textContent); } }); }); const config = { characterData: true, ← watch text node changes subtree: true, characterDataOldValue: true ← capture old value }; observer.observe(element, config); |
Key takeaways
MutationObserver detects DOM changes without polling. Pass configuration options: childList (elements added/removed), attributes (attribute changes), characterData (text changes), and subtree (watch descendants). mutation.addedNodes contains newly added elements. Always call observer.disconnect() when done to prevent memory leaks. Use MutationObserver for frameworks that add content dynamically, monitoring third-party scripts, or watching user interactions that modify the DOM.