JavaScript MutationObserver: Efficient DOM Manipulation

In this article, you are going to learn JavaScript MutationObserver, its syntax, importance of MutationObserver, and providing code examples.

What is MutationObserver JavaScript?

MutationObserver is an in-built JavaScript object that detects changes to the DOM (Document Object Model). It will trigger callbacks, enabling developers to behave promptly to these modifications.

Understanding the Importance of MutationObserver

To make dynamic and interactive web applications, developers usually need to observe and handle changes in the DOM.

Earlier, methods like the Mutation Events were occupied, yet they had performance problems and were eventually deprecated.

MutationObserver, made known in ECMAScript 6 (ES6), fixed these problems and became the new standard.

How MutationObserver Works?

MutationObserver is like an observant that continuously detects the DOM. Whenever any modification occurs, it records the information of the changes, such as the type of mutation, target element, and any approximate data.

This data is passed to the registered callback function, enabling developers to take proper actions.

MutationObserver Syntax

The basic steps for using the syntax MutationObserver are:

First, determine the callback function that will perform when the DOM changes:

function callback(value) {
    //You can perform a code here
}

Second, make a MutationObserver object and pass the callback into the MutationObserver() assembler:

let observerFunc = new MutationObserver(callback);

Third, call the observe() function to start observing the DOM modification.

observerFunc.observe(valueNode, observer);

The observe() function takes two parameters. The first parameter, called “valueNode” performs the root of the node subtree that requires monitoring for modification.

The second parameter, “observer” includes properties that determine which DOM changes should be prompted to the observer’s callback function.

Finally, stop observing the DOM changes by calling the disconnect() method:

observerFunc.disconnect();

The MutationObserver Choice

The second argument of the observe() function enables you to define a choice to describe the MutationObserver:

let options = {
    childList: true,
    attributes: true,
    characterData: false,
    subtree: false,
    attributeFilter: ['attr1', 'attr2'],
    attributeOldValue: false,
    characterDataOldValue: false
};

This example code defines an object named options with different properties that are used to configure a MutationObserver.

This observer is used to observe for changes in the DOM (Document Object Model) of a web page.

Let’s shortly explain each property:

  • childList
    • It shown whether to monitor direct child elements for changes (true).
  • attributes
    • It defines either to observe changes in the attributes of the target element (true).
  • characterData
    • It defines whether to monitor changes in the text content of the target element (false.
  • subtree
    • It defines either to monitor changes in the entire sub-tree under the target element (false).
  • attributeFilter
    • It is an array of attribute names. Only changes to the defined attributes will trigger the observer ([‘attr1’, ‘attr2’]).
  • attributeOldValue
    • It defines either to include the previous value of the attributes in the mutation record (false).
  • characterDataOldValue
    • It defines whether to include the previous value of the text content in the mutation record (false).

Observing changes to child elements

It is supposed to be that you have the following HTML code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JavaScript MutationObserver Tutorial</title>
</head>
<body>
    <ul id="languages">
        <li>HTML</li>
        <li>CSS</li>
        <li>JavaScript</li>
        <li>jQuery</li>
    </ul>

    <button id="btnBegin">Begin</button>
    <button id="btnPause">Pause</button>
    <button id="btnNew">Add New Language</button>
    <button id="btnDelete">Delete the Last Language</button>

    <script src="app.js"></script>
</body>
</html>

This example code is how to apply the childList property of the mutation options object to track changes to child nodes.

To begin, use the querySelector() method to select elements such as the list and buttons. By default, the “Pause” button is disabled.

// selecting list
let list = document.querySelector('#language');

// selecting buttons
let btnNew = document.querySelector('#btnNew');
let btnDelete = document.querySelector('#btnDelete');
let btnbegin = document.querySelector('#btnbegin');

let btnPause = document.querySelector('#btnPause');
btnPause.disabled = true;

Next, create a log() function used to serve as a callback for the MutationObserver.

function log(mutationsExpression) {
    for (let mutation of mutationsExpression) {
        if (mutation.type === 'childList') {
            console.log(mutation);
        }
    }
}

Then, make a new MutationObserver object:

let observerFunc = new MutationObserver(log);

Then, create a log() function intended to function as a callback for the MutationObserver.

Then, initiate the observation of DOM changes to the child nodes of the list element when the “Pause” button is clicked.

This can be done by calling the observe() function, wherein the childList of the options object is set to true.

btnBegin.addEventListener('click', function () {
    observerFunc.observe(list, {
        childList: true
    });
    
    btnBegin.disabled = true;
    btnPause.disabled = false;
});

After that, When the add button is clicked, include a new list item.

let counterSample = 1;
btnNew.addEventListener('click', function () {
    // create a new item element
    let value = document.createElement('li');
    value.textContentSample = `Value ${counterSample++}`;

    // append it to the child nodes of list
    list.appendChild(value);
});

Lastly, When the Remove button is clicked, remove the last child from the list.

btnDelete.addEventListener('click', function () {
    list.lastElementChild ?
        list.deleteChild(list.lastElementChild) :
        console.log('No more child node to delete');
});

Finally, after clicking the “Pause” button, put an end to monitoring DOM changes by using the disconnect() function of the MutationObserver object.

btnStop.addEventListener('click', function () {
    observerFunc.disconnect();    
    // set button states
    btnStart.disabled = false;
    btnStop.disabled = true;
});

Changes to attributes

You can use the attributes property of the options object to monitor any modifications to attributes.

Here’s an example code:

let options = {
  attributes: true
}

You can use the attributeFilter property to observe alterations in one or multiple particular attributes while disregarding the rest.

let options = {
  attributes: true,
  attributeFilter: ['class', 'style']
}

In this example code, the MutationObserver will trigger the callback whenever alterations occur in the class or style attributes.

Conclusion

MutationObserver is capable of responding to alterations in the DOM, enveloping attribute modifications, text content adjustments, and the addition or removal of elements.

This powerful tool allows us to monitor changes made known by different sections of our code and seamlessly integrate with external scripts.

MutationObserver can track any modifications; however, by using the configuration options for “what to observe”, we can optimize its performance by preventing unnecessary callback triggering and conserving valuable resources.

Additional Resources

Leave a Comment