Reacting to DOM Changes With MutationObserver API

DZone 's Guide to

Reacting to DOM Changes With MutationObserver API

Let's explore reacting to DOM changes with the MutationObserver API.

· Integration Zone ·
Free Resource

Image title

Reacting to DOM changes

As developers, we love to build tools. And we love to enhance tools. It may be anything: an extension for the browser, a plugin for some product, and so on. And we need a way to somehow react to DOM changes caused by some external process.

It’s really cool if we have an API to use. But that's not always the case. Sometimes we just need to watch for DOM changes. And we may want to go with a naïve approach: to apply a polling technique with the setInterval API   to check if something was changed within some period of time. This approach is pretty straightforward, but it has some tradeoffs:

  1. It may significantly affect your app’s performance

  2. It becomes harder and harder to maintain the code over time

Here is where MutationObserver comes into play. It is a Web API supported by the majority of modern browsers allowing us to watch for the changes made to the DOM tree and to react on them. Let’s take a look at its API.

You may also like:  How to Track Changes in the DOM Using MutationObserver


According to MDN, the MutationObserver constructor takes only one parameter: a callback function that would be later called on each DOM change for the observed node. This constructor returns a MutationObserver instance with 3 available methods:

  1. observe(): configures the MutationObserver instance to start reacting to DOM changes with provided callback. Takes 2 arguments: required targetNode and an optional config for this node. Obviously, targetNode should be a single node or a root of a subtree to be watched. The config object describes the rules to detect required mutations.
  2. disconnect(): stops the MutationObserver instance from reacting to DOM changes. Observation can be later restarted with another observe() call.
  3. takeRecords(): returns all pending MutationRecords which has not been processed by our callback function and clears mutation queue.

A common use case looks like the following:


Now let’s take a detailed look at config parameter used by the observe() method. This parameter allows us to configure mutation observer to only watch for specific changes. Here is a list of options available for configuration:

  1. attributes:Boolean. Configures the observer to watch for attribute changes on targetNode and its subtree.
  2. attributeFilter:An array of strings. Each string is a name of an attribute to be watched. If you won’t use this property explicitly changes to all attributes would be observed.
  3. attributeOldValue:Boolean. Configures the observer to include the previous value of the watched attribute. The value is available under oldValue key on MutationRecord.
  4. characterData:Boolean. Configures the observer to watch for changes in textual contents of targetNode and its subtree.
  5. characterDataOldValue:Boolean. Configures the observer to include the previous value of the watched text nodes.
  6. childList:Boolean. Configures the observer to watch for the addition or removal of any child nodes under targetNode and its subtree.
  7. subtree:Boolean. Configures the observer to watch for the same changes not only on provided targetNode but on its subtree as well. Uses the same configuration for changes in subtree as for the root.

In order for MutationObserver to work it must know what it should watch for. So at least one of childList, attributes, or characterData must be set to true. Otherwise TypeError exception would be thrown.

Observer Callback

When fired, the provided callback function is called with 2 arguments: an array of MutationRecords representing each change(all changes would be batched if they have happened within some period of time) and an instance of MutationObserver which invoked this callback. Since the observer instance is the same described above we won’t cover it once again. Instead, we focus on the first argument.

Each object in this array represents an individual DOM change and contains the same set of fields:

  1. type:String. The type of the mutation. The value is attributes for an attribute mutation, childList for mutation of subtree and characterData for character data mutation.
  2. target:Node. The node affected by the mutation. The value depends on mutation type: element containing changed attribute for attributes, the node whose descendant changed for childList and CharacterData node for characterData.
  3. addedNodes:NodeList. A list of added nodes. Will be empty if no nodes were added during the mutation.
  4. removedNodes:NodeList. A list of removed nodes. Will be empty if no nodes were removed during the mutation.
  5. previousSibling:Node. Previous sibling of added or removed node.
  6. nextSibling:Node. Next sibling of added or removed node.
  7. attributeName:String. A name of changed attribute for attributes mutation type, null for others.
  8. attributeNamespace:String. Namespace of changed attribute if there is any.
  9. oldValue:String. Depends on mutation type. The value of changed attribute before change for attributes, the data of changed node for characterData and null for childList.

Keep in mind to process all mutations happening you should iterate over mutations array by simply using the forEach method on it.


You can find an interactive example here (it’s hosted on Heroku, so you may need to wait some time until dyno is started). The source code may be found here.

Further Reading

Conditionally Enabling and Disabling DOM Elements

Building an Interactive DOM Benchmark, Preliminary Results

browser api, frontend, frontend developer, javascript, javascript api, javascript development

Published at DZone with permission of Andrey Semin . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}