The Mighty MutationObserver

·

4 min read

I haven't used the MutationObserver often, but it's been so useful when I do. What it does is to watches for changes in the HTML of a page and once you've found them, you can then do something.

For example, you can watch for a class being added to or removed from an element. Or children being added to or removed from an element. And other things, but those are the two I've used it for.

I spend a lot of time using third party libraries and sometimes I want to do something when it's done something. But I can't edit their code. What I can do is to find out what that third party library does and get the MutationObserver to tell me when it's done it.

Which is hard to explain, but let me give you an example. In this one, I have a library that loads more items when you click a button. When those items have been loaded I want to set the focus to the first new added one (by default this library kept the focus on the load more button). Here's the code:

const container = document.querySelector('.container');

// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'childList') {
      // Set focus to the first item in addedNodes
      mutation.addedNodes[0].focus();
    }
  }
};

  // Check when more elements are added
  const observer = new MutationObserver(callback);
  observer.observe(container, { subtree: false, childList: true });

Let's go through this code one bit at a time.

const container = document.querySelector('.container');

This is defining the target as a constant, to be used later. It's the container that all of the items are in. It starts off with 10 items and when you click the button to load more it will add another 10 items.

// Callback function to execute when mutations are observed
const callback = (mutationsList, observer) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'childList') {
      // Set focus to the first item in addedNodes
      mutation.addedNodes[0].focus();
    }
  }
};

Then we have the mutationObserver itself. This is just a function that's called when there is a mutation. It takes two arguments, one is what has been mutated and the other is what is being watched aka observed.

There could be multiple changes aka mutations at once. In our case, we'll have at least 10 of them, one for each item that has been added. So we loop through them.

And then we check the mutation type. There are a few different ones, in this case it's childlist. This means that if you wrote a list of the children of this element, it would have changed. In other words, we added some elements to it.

The reason we check this is that it's possible there have been other changes - so maybe the library changes the class every time the button is clicked. But we don't care about that, we only care about elements being added.

And then we look at what the change is in more detail. In this case, it's a mutation where we've added nodes. The addedNodes are an array, so we can easily pick out the first one to add our focus to.

// Check when more elements are added
  const observer = new MutationObserver(callback);
  observer.observe(container, { subtree: false, childList: true });

And then all we have to do is to setup the observer. And tell it to observe, and what to observe. There are a few things it can observe. In this case we're telling it to watch the childList, but ignore the subtree.

The childList is the direct children of the element. Our element in this case is the container and we want to know when children are being added.

The subtree are all of the children, grandchildren, great-grandchildren etc of the element. Since we know that the new items will be children of the container, we don't need to know if something changes further down the tree.

So that's a basic example for this. It's not the easiest to understand, not least because the docs don't really explain things. But it is short and powerful. And for everything you want to do, someone has probably asked something similar on StackOverflow.