Skip to Content
Course content

85: The MutationObserver API

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've mostly handled events that are triggered by users—clicks, keypresses, submits. But in a modern app, the DOM changes all the time, often because of other scripts, API responses, or third-party libraries. If you need to react when a specific element is added to the page or an attribute changes, you can't just add a click listener. You need the MutationObserver API.

I used to be the person who would just run a setInterval every 100ms to check if an element existed yet. It's a terrible habit. It kills performance and feels like a hack. MutationObserver is the professional way to handle this; it's asynchronous and only fires when actual changes happen.

Setting up a chat window to watch

Let's build something concrete. Imagine we have a chat window. We want to automatically scroll to the bottom whenever a new message is appended to the list, regardless of what piece of code actually added that message. Here is our basic structure:

<div id="chat-container" style="overflow-y: scroll; height: 200px;">
  <ul id="message-list">
    <li>System: Welcome to the chat!</li>
  </ul>
</div>
<button id="send-btn">Send Message</button>

Defining the observer's reaction

The observer takes a callback function. This function receives a list of MutationRecord objects, which tell you exactly what changed. I don't usually need the granular detail of every single single character change, so I'll just check if any nodes were added.

const messageList = document.getElementById('message-list');

const callback = (mutationsList) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'childList') {
      console.log('A new message arrived!');
      const container = document.getElementById('chat-container');
      container.scrollTop = container.scrollHeight;
    }
  }
};

const observer = new MutationObserver(callback);

The mistake I always make with config

Now, I need to tell the observer which element to watch and what specifically to look for. In my first attempt at this, I did this:

// This is the wrong way!
observer.observe(messageList);

And... nothing happened. I spent five minutes wondering why my callback wasn't firing. The problem is that observe() requires a configuration object as the second argument. You can't just point it at an element; you have to tell it what about that element matters. Do you care about attributes? Text content? Child elements?

Since we are looking for new <li> tags, we need to set childList: true. I'll also add subtree: true just in case the messages are wrapped in another container inside the list.

// The corrected version
const config = { 
  childList: true, 
  subtree: true 
};

observer.observe(messageList, config);

Testing it in action

To make sure this actually works, let's simulate a message being added. I'm not going to use the button for this; I'll just push a new element into the DOM using a script to prove the observer is doing the heavy lifting.

// Simulating an incoming message from a websocket or API
setTimeout(() => {
  const newMessage = document.createElement('li');
  newMessage.textContent = 'User: Hey, is this working?';
  messageList.appendChild(newMessage);
}, 2000);

Now, when that appendChild runs, the observer notices the childList change, triggers the callback, and scrolls the container. It's clean, decoupled, and efficient. Just remember: once you're done with an observer, call observer.disconnect() to prevent memory leaks, especially in single-page applications where elements are constantly being destroyed and recreated.




📋 Practical Task

Build a "Dark Mode" Attribute Watcher

Create a small project where you use a MutationObserver to watch the <body> element for changes to its class attribute.

  • Create a button that toggles a .dark-theme class on the <body>.
  • Implement a MutationObserver that detects when the class attribute changes.
  • When the observer detects the change, have it log "Theme changed to Dark" or "Theme changed to Light" to the console based on whether the class is present.
  • Ensure your MutationObserver configuration specifically targets attributes.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.