MutationObserver in JS Overview

The MutationObserver interface offers the potential to observe for adjustments being made to the DOM tree. Suppose some data in the website is rendering with 3 seconds delay, it could be rendering by sever, so on/after page load. As a result of this delay, we are not able to get data in our dom, and we are not able to apply javascript to target that server side DOM element as it is not present in DOM at the time of first page load, So to observe that DOM changes and apply javascript at that time when server side DOM element got injected in HTML page we use mutation observer. MutationObserver
is a built-in object that observes a DOM element and fires a callback when it detects a change.
Set-up
First, create an observer with a callback-function. This callback is executed anytime any change occurs in the DOM:
let observer = new MutationObserver(mutationCallback);
And then attach it to a DOM node. And which changes in DOM to be observed are mentioned in the config/options:
observer.observe(node, config);
config is an object with boolean options “what kind of changes to react on”:
let config = {
childList: true,
attributes: true,
characterData: false,
subtree: false,
attributeFilter: ['attr1', 'attr2'],
attributeOldValue: false,
characterDataOldValue: false
};
childList
– changes in the direct children ofnode
,subtree
– in all descendants ofnode
,attributes
– attributes ofnode
,attributeFilter
– an array of attribute names, to observe only selected attributes.characterData
– whether to observe text content of that node (node.data
- but only text content of that node, not applicable if the text content is in a child node).
other options:
attributeOldValue
– iftrue
, pass both the old and the new value of attribute to callback, otherwise only the new one (needsattributes
option),characterDataOldValue
– iftrue
, pass both the old and the new value ofnode.data
to callback, otherwise only the new one (needscharacterData
option).
Observing Changes
Then after any change (as indicated in the config) takes place, the callback
is executed. Changes are passed in the first argument as an array of MutationRecord objects, and the observer itself as the second argument.
Why an array? Because when DOM is updated, the changes may occur at various nodes. After page load, when a new script is run or response from backend is received, changes may occur at various DOM nodes. All these changes that have taken place in one DOM update, to uniqely identify each change, an array of MutationRecord is given where each MutationRecord contains the details of each single change that are observed.
Well, for testing, in the script that will be injected after page load, you may add setTimeout to a node that you are observing and you’ll see that change is not inside array. Because, that setTimout took place in DOM in another DOM update, not that same update that updated the DOM after injecting the script.
function mutationCallback(entries){
entries.forEach(entry =>{
// operations
console.log(entry);
})
}
MutationRecord objects have properties:
type
– mutation type, one of"attributes"
: attribute modified"characterData"
: data modified, used for text nodes,"childList"
: child elements added/removed,
target
– where the change occurred: an element for"attributes"
, or text node for"characterData"
, or an element for a"childList"
mutation,addedNodes/removedNodes
– nodes that were added/removed,previousSibling/nextSibling
– the previous and next sibling to added/removed nodes,attributeName/attributeNamespace
– the name/namespace (for XML) of the changed attribute,oldValue
– the previous value, only for attribute or text changes, if the corresponding option is setattributeOldValue
/characterDataOldValue
.
WaitForElement Function
Here’s a common waitForElement function to apply with mutationObserver. It checks if the element is present initially. If not present on page load, then adds the mutationObserver to the document body for checking any DOM update on children
and subtree
. When the element is found it returns promise true and disconnects the observer. So, by getting the promise resolved response, we later implement our functionality in another function.
function waitForElement(selector) {
// check if element available on page load
return new Promise((resolve) => {
if (document.querySelector(selector)) {
return resolve(element);
}
// createthe observer and decalre the callback function
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
observer.disconnect();
}
});
// assign the observer to body
observer.observe(document.body, {
childList: true,
subtree: true,
});
});
}
Addy Osmani (reference 4) put some intermediate level use case with mutation observer. The article can be a good read to have advanced understanding. Here, I am mentioning some advanced pointers he mentioned at the end of the article.
About garbage collection and references are important when it comes to discussing any form of observation, so let’s talk about it relative to Mutation Observers.
Closing the tab (or window) for your page will automatically disconnect any of the Mutation Observers for elements in that document. You see, Mutation Observers hold weak references to each target node. If the node happens to get destroyed the observed no longer notifies the page about mutations.
The DOM node in question holds a strong reference to your Mutation Observer however – so if the target remains, you should probably avoid destroying observer instances without disconnecting them. Where possible, disconnect your observers on page unload.
I’ve been talking them up, but Mutation Observers are not without their faults and the following represents some of their known limitations:
Mutation Observers listen for changes to DOM nodes but the current state of form elements are not accurately reflected in the DOM. They don't reflect their "live" or "true" value back into the attribute storage. The "value" attribute is really just "defaultValue". Similarly the internal state of elements such as the value of a
<textarea>
or whether<details>
is collapsed is state that needs to be observed separately and is generally exposed in the form of events. For example, with input elementsinput.getAttribute('value')
returnsnull
rather than the current value. Observers thus can't observe this type of change and you end up needing to write your own input/textarea change detection usinginput
/keyup
event listeners instead. Many developers I've seen do this rely on polling to detect when the text has changed every 50–200 ms.MutationObservers are unable to detect changes to CSS styles (like hover state).
Timestamps for mutations are not included in the change records.
Finally, for examples and more detailed understanding, I am referring to the articles I took help from:
1. Mutation Observer
2. JavaScript MutationObserver
3. How to use Javascript Mutation observer
4. Detect, Undo And Redo DOM Changes With Mutation Observers
Subscribe to my newsletter
Read articles from shohanur rahman directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
