The concept
MutationObserver informs you whenever a change (mutation) on a element or its child elements occurs. A change can be a modification of an element’s attributes, its text or if there was a child node appended or removed.
A very powerful mechanism! But it comes at a cost in terms of performance. That’s why this API offers many detailed configuration options and methods that you should use carefully.
Observation is expensive. Be as specific as you can be when defining what to observe.
Just me
Example: Observing an element and reacting to changes in two steps
In this example we want to observe the following element for changes:
<p id="observed">I am observed</p>
Step 1: Create MutationObserver instance and callback
The callback answers two questions: “What has changed?” and “How do I want to react to these changes?”
// defining what to do when an observed change happened let callback = (mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { console.log('A child node has been added or removed.'); } else if (mutation.type === 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.'); } } };
Now we create an instance of MutationObserver and pass in the callback.
let observer = new MutationObserver(callback);
Step 2: Call observe
on the MutationObserver
Calling observe will answer two questions: “Which element do I want to observe?” and “What in particular do I want to observe on that element?”. More on that later.
let observedElement = document.getElementById("observed"); // configure what to observe on the element let config = { attributes : true, childList : true, subtree : true }; observer.observe(observedElement, config);
Now let’s trigger some changes:
// changing style will trigger mutation of type 'attributes' observedElement.style.color = "red"; // appending a child will trigger mutation of types 'childList' let child = document.createElement('span'); observedElement.appendChild(child); // appending a child to the child will trigger mutation of types 'childList' again child.appendChild(document.createElement('div'));
Important: The MutationObserver callback is invoked only after the end of the script execution, that is: Not after line 2, not after line 6 but after line 10.
Defining what changes to watch for
Watching for changes can be expensive (in terms of performance, CPU and RAM resources). That’s why it is a good idea to define exactly what changes you want to observe. That’s what the second parameter of observe
is for.
Side note: Mozilla’s doc is specifying this object as optional, but in my tests you get an exception if you do not specify it:
Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': The options object must set at least one of 'attributes', 'characterData', or 'childList' to true.
Observing specific attributes
We use attributeFilter
to specify that we only want to observe the attribute class
for changes. Of course any mutation to style
will not trigger our MutationObserver callback.
// only observe 'class'-attributes let config = { attributeFilter : ['class'], attributes : true, }; // we observe now, but callback is only triggered after script is done executing observer.observe(observedElement, config); // no mutation is triggered, because we do not observe 'style' this time observedElement.style.color = "red";
Observing text changes
Yes, we can observe changes of an element’s text node. In the following example we change the text from “I am observed” to “I was changed”. Observing changes of text nodes can be done using characterData
property on the config object. We even set an option that allows us to record the old text value by setting characterDataOldValue
to true
.
let observedElement = document.getElementById("observed"); // defining what to do when an observed change happened let callback = (mutationsList, observer) => { for (let mutation of mutationsList) { if (mutation.type === 'characterData') { console.log(`Character was changed from '${mutation.oldValue}' to '${mutation.target.data}'`); } } }; let observer = new MutationObserver(callback); // we configure to observe text changes let config = { characterData: true, characterDataOldValue : true, subtree: true }; observer.observe(observedElement, config); observedElement.firstChild.textContent = "I was changed"; // Side note: The following would not trigger a characterData mutation, but a ChildList event observedElement.innerHTML = "I was changed by adding a new text node";
Multiple observations on a single MutationObserver
No problem, you can call observe()
multiple times on the same MutationObserver
. You might want to do that to watch for changes of different parts of the DOM tree and/or different types of changes. Remember, both can be specified as parameter of the observe method, whereas the MutationObserver instance merely processes what to do when a change happened.
But here is a catch: If you call observe()
with a node that’s already being observed by the same MutationObserver
, all existing observers are automatically removed from all targets being observed before the new observer is activated. On the contrary, if the same MutationObserver
is not already in use on the target, then the existing observers are left alone and the new one is added.
[ciu_embed feature=”mutationobserver” periods=”-1,current,+1,+2″]
No comments yet.