Detecting DOM change using Mutation observer

- 11 mins



Detecting and get notified when dom changes is a common scenario for developers. Previously it could be done using Mutation events. But it had a huge performance degradation and stability issue (see here). That’s why it has announced as deprecated and recommended to avoid. In future release of the browsers, this might be removed from their engines too.
On 2011 a new proposal for MUTATION OBSERVER had announced. It resolved the performance issue at the same time keeping the same functionality.

Today we’ll implement a basic mutation observer and will discuss about its structure.


What will we do?

Do it yourself - Step 1

Let’s try it out.

Initially create a html boilerplate. Create a css file style.css and a js file mutation.js on same directory.

	<!DOCTYPE html>
		<meta charset="utf-8">
		<title>Mutation Observer 101</title>
		<link rel="stylesheet" type="text/css" href="style.css">
	<script type="text/javascript" src='mutation.js'></script>

Now update html markup within body. Here div with the id changeMe contains actual data we will mutate. Next div with id log will show the log data whenever data changes on upper div.

	<div class="container">
		<div id="changeMe">
				<li data-id="1">Hello, My name is Nabil Ahmad.</li>
				<li id='oldId' data-id="2">Nice to meet you.</li>
				<li data-id="3">Thank you.</li>
		<div id="log">

Next add some styling code. Add following css on style.css

	ul, ol{line-height: 2em;}
	#log{margin-top: 1%;}
		background: purple;
		color: #fff;
		padding: 10px;
	.type{background-color: chartreuse;}
	.change{background-color: antiquewhite;}

So our basic boilerplate is ready. Next we will start with mutation.js file.

Do it yourself - Step 2

Add an array of corresponding spanish translation for each list text.

	const translatedText = ["Hola, Me llamo Nabil Ahmad.", "Mucho gusto.", "Gracias."],

Set two variable targetNode as text selector and logNode for log destination.

	const translatedText = ["Hola, Me llamo Nabil Ahmad.", "Mucho gusto.", "Gracias."],
	targetNode = document.querySelectorAll("#changeMe ol li"),
	logNode = document.querySelector("#log ul")

Let’s add some variable which we will use later.

	let translationOp, loop, index = 0;

Do it yourself - Step 3

The function changeText will pick the text from list using targetNode selector and current index. Index is initialized with 0. So index will be incremented by 1 when call next and upon calling the same function whenever it become eqal to loop (wait… what is it???!!!) we call translationOp to stop.
First if condition states that when it is the second item from list (remember list index start from 0. So 1 means second list item) we change it’s id to ‘newId’

	function changeText(){
		targetNode[index].innerText = translatedText[index];
		if(index === 1){
			targetNode[index].setAttribute('id', 'newId');

		index += 1;
		if(index === loop){

If you follow along you’ve already recognized that a setInterval method is calling the function and translationOp variable is keeping the setInterval as a reference. and if it equals to loop (again !!!) it will stop.

Until now if it looks confusing, don’t stop. Continue the reading. I’m sure you will catch up within a minute.

Here are the initialization function init and the mysterious loop which is actually a normal variable has the length of our list of text. We’re creating a setInterval method which is calling our changeText each 3s until stopped.

	function init(){
		loop = targetNode.length;
		translationOp = window.setInterval(changeText, 3000);
	// initialize it

I hope this section is clear. If not run the code. Revise whole code top to bottom carefully. For reference here’s the final version on codepen

Do it yourself - Step 4

So far so good!

Now we will create the mutation observer.

First add observerTarget selector and a variable called observer on top of the script.

	// of the const variables
	observerTarget = document.querySelector("#changeMe");

	// of the initializer variables

Now implement observeChange function. The whole code is already documented. So I am giving a short brief on that.-

	function observeChange(){
		//callback function
		var mutationNotifier = function(mutationsList) {
		    mutationsList.forEach(function(mutation, index){
		    	// log mutation

		    if(index === loop) stopObserver();

		//create an instance with callback function
		observer = new MutationObserver(mutationNotifier);

		//create options
		var config = {
			attributes: true, // observe attributes (ex. id, class)
			attributeOldValue: true, // record value before mutation
			attributeFilter: ["id"], // only observe this list of attributes
			characterData: true, // observe target node's data
			characterDataOldValue: true, // record target node's data before mutation
			childList: true, // observe all child elements including text nodes
			subtree: true // target node & all of its descendants, false = only target node

		//call mutationobserver
		observer.observe(observerTarget, config);

		//stop observer when done
		var stopObserver = function(){

Let’s initialize the observeChange from main function init.

	function init(){
		// code

Do it yourself - Step 5

Now observer is watching. But how do we know it’s working?

Let’s show some log output to watch if it’s working or not.

Create a addLog function which will add our log item on logNode (previously declared) target.

	function addLog(logItem){
		logNode.insertAdjacentHTML('beforeEnd', logItem);

Now add a log after calling mutation observer.

	// call mutation observer
	addLog("<li class='log'>Observer started!</li>");

Also, add a log within stopObserver function when observer disconnect.

	var stopObserver = function(){
		addLog("<li class='log'>Observer stopped!</li>");
		// rest code

The only place left is within mutation observer callback function mutationNotifier. Each mutation object has a set of value which is return after mutation/change happens. Detail on each key has been given below.

Within switch method, I am checking for type of mutation and based on that adding to log.

	var mutationNotifier = function(mutationsList) {
	    mutationsList.forEach(function(mutation, index){

	    	// newly added

	    	* Mutation record/result (mutation) *
			* addedNodes: Return the added nodes [NodeList, default: []]
			* attributeName: Return the attribute name that changed [String, default: null]
			* attributeNamespace: Return the namespace of the changed attribute [String, default: null]
			* nextSibling: Return the nextSibling of changed node [Node, default: null]
			* oldValue: Return the nextSibling of changed node [Node, default: null]
			* previousSibling: Return the nextSibling of changed node [Node, default: null]
			* removedNodes: Return the removed nodes [NodeList, default: []]
			* target: Return the target node where mutation happens [Node, default: null]
			* type: Return the type of mutatation (ex. 'attributes', 'childList') [String]

	    		case "attributes":
	    			addLog("<li class='log'><span class='type'>"+ mutation.type +"</span>List <span class='change'>"+ + "</span> Attribute <span class='change'>" + mutation.attributeName + "</span> changed to <span class='change'>" +[mutation.attributeName] + "</span> (was <span class='change'>" + mutation.oldValue + "</span>) </li>");
	    		case "childList":
	    			addLog("<li class='log'><span class='type'>"+ mutation.type +"</span> List <span class='change'>"+ + "</span> changed</li>");

	    // code

That’s it. We’ve implemented a basic mutation observer and observe the text change. Please check the resources section for complete code link and other few resources I recommend.



This mutation observer is really helpful for javascript developers as well as for a/b test developers.

comments powered by Disqus
rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora