generality
MutationObserver is a Web API provided by modern browsers to detect changes in the DOM. With this API, one can listen for newly added or removed elements, property changes, or changes in the textual content of the elements.
Why do you want to do that?
There are quite a few cases where the MutationObserver API can really help. For example:
- You want to notify your web application visitor that some changes have occurred on the page he is currently visiting.
- You’re working on a JavaScript framework that loads dynamic JavaScript modules based on how the DOM changes.
- You may be working on a WYSIWYG editor trying to implement the undo / redo functionality. By leveraging the MutationObserver API, you know what changes were made at any given moment, so you can easily undo them.
How to use MutationObserver
Deploying MutationObserver into your application is pretty easy. You need to create a MutationObserver variable by passing it a function that will be called every time a mutation occurs. The function’s first argument is the set of all mutations that have occurred in a single batch. Each mutation provides information about its type and what changes have occurred.
1 2 3 4 5 6 7 | <span class="token keyword">var</span> mutationObserver <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MutationObserver</span> <span class="token punctuation">(</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token parameter">mutations</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> mutations <span class="token punctuation">.</span> <span class="token function">forEach</span> <span class="token punctuation">(</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token parameter">mutation</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> mutation <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
The created object has three methods:
- observe- start listening for changes. Take two arguments – the DOM element you want to observe and an implementation object
- disconnect – stop listening for changes
- takeRecords – returns the last series of changes before the callback was triggered.
1 2 3 4 5 6 7 8 9 10 | <span class="token comment">// Starts listening for changes in the root HTML element of the page.</span> mutationObserver <span class="token punctuation">.</span> <span class="token function">observe</span> <span class="token punctuation">(</span> document <span class="token punctuation">.</span> documentElement <span class="token punctuation">,</span> <span class="token punctuation">{</span> attributes <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> characterData <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> childList <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> subtree <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> attributeOldValue <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> characterDataOldValue <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Now, let’s say you have some very simple divs in the DOM:
1 2 | <div id="sample-div" class="test"> Simple div </div> |
Using jQuery, you can remove the class from that div:
1 2 | $("#sample-div").removeAttr("class"); |
After calling, mutationObserver.observe (…) we should see the change in the respective MutationRecord’s console:
This is a mutation caused by class deletion. And finally, to stop observing the DOM after it’s done, you can do the following:
1 2 3 | // Stops the MutationObserver from listening for changes. mutationObserver.disconnect(); |
Alternative solution
MutationEvents
In 2000, the MutationEvents API was introduced. While useful, mutation events are fired on every change in the DOM, which in turn causes performance issues. Today, MutationEventsAPI is no longer in use and modern browsers will soon stop supporting completely.
CSS Animation
A slightly odd alternative is one based on CSS animations. It sounds a bit confusing. Basically, the idea is to create an animation that will be triggered when an element has been added to the DOM. The moment the animation starts, the animationstart event will be fired: if you’ve attached an event handler to the event, you’ll know exactly when the element has been added to the DOM. The execution duration of the animation should be so small that the actual user cannot see it. First, we need a parent element, inside of which we want to listen for element insertion:
1 2 | <div id=”container-element”></div> |
To handle element insertion, we need to set up a series of keyframe animations that will start when the element is inserted:
1 2 3 4 5 | @keyframes nodeInserted { from { opacity: 0.99; } to { opacity: 1; } } |
With keyframes created, the animation needs to be applied on the elements you want to hear. Note the small intervals – they’re stretching the animation footprint in the browser:
1 2 3 4 5 | #container-element * { animation-duration: 0.001s; animation-name: nodeInserted; } |
This adds animation to all child nodes of the container-element. When the animation ends, the insertion event is fired.
We need a JavaScript function that will act as an event handler. The event.animationName function must be checked to make sure it’s the animation we want.
1 2 3 4 5 6 7 | <span class="token keyword">var</span> <span class="token function-variable function">insertionListener</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token parameter">event</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Making sure that this is the animation we want.</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> event <span class="token punctuation">.</span> animationName <span class="token operator">===</span> <span class="token string">"nodeInserted"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">"Node has been inserted: "</span> <span class="token operator">+</span> event <span class="token punctuation">.</span> target <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
1 2 3 4 5 | document <span class="token punctuation">.</span> <span class="token function">addEventListener</span> <span class="token punctuation">(</span> “animationstart” <span class="token punctuation">,</span> insertionListener <span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// standard + firefox</span> document <span class="token punctuation">.</span> <span class="token function">addEventListener</span> <span class="token punctuation">(</span> “MSAnimationStart” <span class="token punctuation">,</span> insertionListener <span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// IE</span> document <span class="token punctuation">.</span> <span class="token function">addEventListener</span> <span class="token punctuation">(</span> “webkitAnimationStart” <span class="token punctuation">,</span> insertionListener <span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Chrome + Safari</span> |
MutationObserver offers several advantages over the above solutions. In essence, it keeps track of every possible change in the DOM, and it’s more optimized. On top of that, MutationObserver is supported by all modern browsers, along with a few polyfills using MutationEvents.