Use MutationObserver to monitor DOM changes

Network applications are increasingly complex on the client side, which is caused by many factors, such as the need for richer interface interactions to provide more complex application functions, real-time computing, and so on.

The increasing complexity of web applications makes it impossible to know the accurate state of the interactive interface at a given moment in its life cycle.

If you are building some framework or a library, it will be more difficult, for example, you can’t monitor the DOM to respond and perform some specific operations.

Overview

MutationObserver is a web interface provided by modern browsers to detect DOM changes. You can use this interface to listen for new or deleted nodes, property changes, or text node content changes.

What can I do?

You can use the MutationObserver interface handy in the following situations. for example:

  • Notify the user of some changes to the page they are currently on.
  • Dynamically load JavaScript modules based on DOM changes by using some new awesome JavaScript frameworks.
  • Maybe when you are developing a WYSIWYG editor, use the MutationObserver interface to collect changes at any point in time, so as to easily implement the undo/redo function.

These are just a few of the MutationObserver usage scenarios.

How to use MutationObserver

Integrating MutationObserver in an application is fairly simple. A MutationObserver instance is initialized by passing a function as a parameter to the constructor MutationObserver , which will be called every time the DOM changes. The first parameter of the function of MutationObserver is the set of all DOM mutations in a single batch. Each change contains the type of change and the change that occurred.

var mutationObserver = new MutationObserver(function(mutations) {
  mutations. forEach(function(mutation) {
    console. log(mutation);
  });
});

The created instance object has three methods:

  • observe – Start observing DOM changes. Receives two parameters – the DOM node to watch and a configuration object.
  • disconnect – stop listening for changes.
  • takeRecords – Returns the latest batched DOM changes before firing the callback.

The following is the code snippet to start listening:

// Start listening to HTML changes in the root element of the page.
mutationObserver. observe(document. documentElement, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
});

Now, suppose you write a simple div element:

<div id="sample-div" class="test"> Simple div </div>

You can use jQuery to remove the div’s class attribute:

$("#sample-div").removeAttr("class");

When calling mutationObserver.observe(…), you can start monitoring DOM changes.

Every time a DOM change occurs, each MutationRecord log information will be printed:

This change was caused by the removal of the class attribute.

Finally, if you want to stop listening to DOM changes, you can use the following method:

// MutationObserver stops listening to DOM changes
mutationObserver. disconnect();

For now, MutationObserver browser compatibility is fine:

Alternative method

However, MutationObserver was not widely used before. So, when there is no MutationObserver, how do developers solve the problem of monitoring DOM changes?

There are several methods available:

  • Poll
  • MutationEvents
  • CSS animation

Poll

The simplest and most crude way is to use polling. Using the browser’s built-in setInterval web interface, you can create a scheduled task to periodically check whether the DOM has changed. Of course, this approach will significantly slow down the performance of the web application/website.

In fact, this can be understood as dirty checking. If you have used AngularJS, you should have seen the performance problems caused by its dirty checking.

MutationEvents

Back in 2000, the MutationEvents API was introduced. While it works, every single DOM change triggers a mutation event, which again creates performance issues. Now, the MutationEvents interface has been deprecated, and in the near future, all modern browsers will stop supporting this interface.

Here is the browser compatibility of MutationEvents :

CSS animation

Relying on CSS animations is a somewhat novel alternative. This might sound confusing. Basically, the idea is to create an animation that will be triggered once an element is added to the DOM. The animationstart event is triggered when the CSS animation starts: if you add an event listener to this event, you can know exactly when to add elements to the DOM. The running time period of the animation must be very short so that the user cannot perceive it, that is, the experience is better.

First, a parent element is needed to listen for node addition events:

<div id="container-element"></div>

In order to handle the addition of nodes, it is necessary to create a keyframe sequence animation that starts when a node is added:

@keyframes nodeInserted {
 from { opacity: 0.99; }
 to { opacity: 1; }
}

After creating keyframes, apply animation to the elements that need to be monitored. Note that short duration – the animation trail will be very smooth on the browser side (i.e. the user won’t feel the animation happening):

#container-element * {
 animation-duration: 0.001s;
 animation-name: nodeInserted;
}

This will animate all descendant nodes of the container-element . When the animation ends, the insertion event is fired.

We need to create a function as an event listener. Inside the function, the event.animationName code must be used to check at the beginning to make sure it is the animation we are listening to.

var insertionListener = function(event) {
  // Make sure it is the monitored animation
  if (event. animationName === "nodeInserted") {
    console.log("Node has been inserted: " + event.target);
  }
}

Bind event listeners to the parent element:

document.addEventListener(“animationstart”, insertionListener, false); // standard + firefox
document.addEventListener(“MSAnimationStart”, insertionListener, false); //IE
document.addEventListener(“webkitAnimationStart”, insertionListener, false); // Chrome + Safari

Event delegation is used here.

Browser support for CSS animations:

MutationObserver has several advantages over the above alternatives. Essentially, it listens for every possible change to the DOM and is more performant because the callback event is fired only after a batch of DOM changes. In conclusion, MutationObserver is very compatible and has some shims that use MutationEvents under the hood.