JS Tip of the Day: MutationObserver

MutationObserver
Level: Intermediate

The Web API has a number of observer utilities that allow you to watch for changes or certain occurrences that happen within the DOM or web browser. One of these is the MutationObserver. It allows you to watch for changes in DOM elements including:

  • Adding or removing of child elements
  • Changes in element text
  • Adding, removing, or changing of attributes

A MutationObserver works somewhat like a cross between a proxy and addEventListener(). When created, it takes a single callback which gets notified when mutations occur. Then, from the MutationObserver instance, you can observe certain elements in the DOM that will in turn trigger the callback when mutations are recognized.

let observer = new MutationObserver(callback);
observer.observe(element, options);

The options passed to observe() indicate what mutations are being watched for and how the results of those mutations are reported back to the observer’s callback. Options include:

  • attributeFilter
  • attributeOldValue
  • attributes
  • characterData
  • characterDataOldValue
  • childList
  • subtree

Consider the following super-intricate and absolutely revolutionary web app that adds kittens to the screen when you click.

addEventListener('click', event => {
    if (event.shiftKey) {
        if (event.target.tagName === 'IMG') {
            event.target.remove();
        }
    } else {
        let width = 170;
        let height = 170;
        let img = document.createElement('IMG');
        img.src = `https://placekitten.com/${width}/${height}`;
        Object.assign(img.style, {
            position: 'absolute',
            left: (event.clientX - width / 2) + 'px',
            top: (event.clientY - height / 2) + 'px'
        });
        document.body.appendChild(img);
    }
});

If someone wanted to add analytics to report on when the user added or removed kittens using this code, it could be instrumented to do so, modified to add hooks into each of those different operations allowing them send out messages to the appropriate logging systems. However, since the changes ultimately boil down to DOM updates, all of that can be skipped in favor of using a MutationObserver. With a MutationObserver, you can spy directly on the DOM without having to dig into the implementation of the existing code that’s making the changes to it. Here is how that might look.

let imgAnalytics = new MutationObserver(mutations => {
    for (let mutation of mutations) {
        for (let added of mutation.addedNodes) {
            if (added.tagName === 'IMG') {
                console.log('User added image');
            }
        }
        for (let removed of mutation.removedNodes) {
            if (removed.tagName === 'IMG') {
                console.log('User removed image');
            }
        }
    }
});
imgAnalytics.observe(document.body, {
    childList: true
});

This imgAnalytics MutationObserver will log to the console whenever an image element is added or removed from the document.body element, acting as analytics reporting for the soon-to-be wonderfully successful and lucrative click for kittens app.

More info:

1 Like