Back

Exploring the Three DOM Observer APIs

Exploring the Three DOM Observer APIs

Various techniques can be used to achieve responsiveness in modern web development, from media queries to container queries, either through CSS or JavaScript. One such technique is using JavaScript’s DOM Observer APIs to continuously observe and handle changes in the DOM. This article explores what DOM observers are and the three DOM observer APIs, examining their concepts, usage, and browser support.


title: Exploring the Three DOM Observer APIs


DOM observers are special JavaScript APIs that are suffixed with the ‘Observer’ keyword and whose purpose is to listen for certain changes in the DOM. Unlike the CSS media query, which focuses solely on observing change in the viewport size, these observer APIs are used to enhance responsiveness in relation to specific elements. Currently, there are three DOM observer APIs, namely:

These observers monitor different aspects of dynamic changes in the DOM, allowing for a more responsive web application. Let’s examine each one individually.

Resize Observer

The Resize Observer API tracks an element’s size or dimension change, with notifications sent to the observer each time the observed element’s size changes, which can then be used to respond to those changes. It provides an optimized solution for updating specific characteristics in the DOM in relation to elements whose size changes due to user interaction and dynamic content inputs.

Concept

The ResizeObserver(callback) constructor is called to create a new ResizeObserver instance. It takes in a callback function that receives an array of objects known as the ResizeObserverEntry. The ResizeObserver instance provides three different methods for controlling the observation of an element.

  1. observe(target, options): Initiates the observation of an element. It takes in two arguments:

    • target - The element to be observed.

    • options - An optional object that currently takes in only the box property used to specify the box model of the element to be observed, with its value being any of the following: “content-box”, “border-box”, or “device-pixel-content-box”.

  2. unobserve(target): Terminates the observation of the target element being observed.

  3. disconnect(): Ends the observation of all elements currently being observed by the Resize Observer in the DOM.

Example Usage

Let’s examine how we can use the Resize Observer API in our application. In this example, we want to observe the change in dimension of a resizeable textarea element and change its text color and font size accordingly.

To get started, inside our HTML file, we create a textarea element with its font size set to 30px, as well as two buttons, which we will use to observe and unobserve the size changes in the textarea.

<textarea id="text-area" style="font-size: 30px">This textarea is currently not being observed</textarea>

<div id="container">
  <button id="btn-observe">Observe</button>
  <button id="btn-unobserve">Unobserve</button>
</div>

Next, inside our JavaScipt file, we include the following code:

const textarea = document.getElementById("text-area");
const observeBtn = document.getElementById("btn-observe");
const unobserveBtn = document.getElementById("btn-unobserve");

const observer = new ResizeObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.contentRect.width < 300) {
      entry.target.style.fontSize = "16px";
      entry.target.style.color = "green";
 } else {
      entry.target.style.fontSize = "30px";
      entry.target.style.color = "black";
 }
 });
});

observeBtn.onclick = () => {
  observer.observe(textarea);
  textarea.value =
    "This width of this textarea is currently being observed for changes";
};

unobserveBtn.onclick = () => {
  observer.unobserve(textarea);
  textarea.value = "This textarea is currently unobserved";
};

In the above code, a new ResizeObserver() object is created and passed a callback function, which takes in an array argument containing all of the observer’s entries.

Each ResizeObserverEntry reports the content box of its element through the contentRect property, which we can then use to access that element’s width, height, and position.

ResizeObserverEntry object

Next, the entries array is looped through, and each entry is checked if its contentRect.width is less than 300px. If it is, the entry’s font size is set to 16px and its text color is changed to green. Otherwise, its initial 30px font size black text color is set.

After that, the observe() method is called in the click event handler of the observeBtn element, to start listening for dimension changes in the textarea element, and when the unobserveBtn is clicked, the unobserve() method is called, to stop tracking the width of the textarea. Below is the code output.

resize observer code example output

Note: Once an element stops being observed, the element remains in its current state, not returning to the initial state before it was observed. This is because the ResizeObserver only tracks for changes while it is actively observing an element.

Browser Support

The Resize Observer API is used to monitor element resizes and is supported by all major web browsers. Feel free to check out the complete browser support table.

Intersection Observer

The Intersection Observer API is a powerful tool that tracks the intersection of a target element with the viewport or other specified elements in the DOM. This API enables easy implementation of features such as scroll-based animations, infinite scrolling, lazy-loading of contents, and much more.

Concept

The IntersectionObserver(callback, options) constructor is called to create a new IntersectionObserver instance. The constructor takes in the following two arguments:

  1. callback: A required function that specifies the actions to be taken when an intersection is detected. It takes in an array of IntersectionObserverEntry objects.

  2. options: An optional object used in configuring the behavior of the observer. It accepts the following three properties:

    • Root: Specifies where to observe the intersection. Possible values are null (default) indicating the viewport or any valid DOM element.

    • threshold: Specifies from what percentage of the target element’s intersection on the root element the observer should be called. Its value ranges from 0 to 1, with 0 meaning the target element is barely intercepting and 1 meaning full interception.

    • rootMargin: Sets a margin for the root element. It takes a string of negative or positive values, which are used to reduce or increase the space between the root and the target element, respectively.

Similar to the Resize Observer, the IntersectionObserver instance also provides the same three methods for controlling the observation of an element. However, its observe() method receives only the target element as an argument.

Each entry object in the IntersectionObserverEntry array provides several properties that can be used to obtain information about the target element’s intersection with its root.

IntersectionObserverEntry object

Let’s understand a few of the commonly used properties here:

  • intersectionRatio: Reports the percentage of the target element that is intercepting the root. A value of 1 means 100% interception, and a value of 0 means no interception.

  • isIntersecting: A boolean value that is true if any percent of the target element is intercepting its root and false if no percent of the element is intercepting.

  • target: The current target element being observed.

Example Usage

To better understand how to use the Intersection Observer API, we will create a basic webpage that mimics the social media feed feature by implementing its infinite scroll and lazy-loading effects.

To get started, we include the following code in our HTML file:

<h1>Social Media Feed</h1>

<div id="posts-container">
  <div class="post">
    <h2>Post 1</h2>
  </div>
  <div class="post">
    <h2>Post 2</h2>
  </div>
  <div class="post">
    <h2>Post 3</h2>
  </div>
  <div class="post">
    <h2>Post 4</h2>
  </div>
  <div class="post">
    <h2>Post 5</h2>
  </div>
  <div class="post">
    <h2>Post 6</h2>
  </div>
    <!-- More posts will be added here dynamically -->
</div>

The above code renders six post elements on the initial page load, rather than loading an endless number of posts at once which can significantly impact page load times and performance. We initially load six posts and as the user scrolls, more posts will be loaded onto the page.

Moving into our JavaScript file, we first need to declare a function that will be used to create the new post element, which will be uploaded to the DOM as the user scrolls.

const postContainer = document.getElementById("posts-container");

function newPost() {
  let post = document.createElement("div");
  let content = document.createElement("h2");
  post.className = "post";
  content.textContent = `Post ${postContainer.children.length + 1}`;
  post.appendChild(content);
  postContainer.appendChild(post);
}

This function, when called, creates a new div element as the post and a child h2 element to display the post number. In a real social media application, this function would probably be to fetch post data from a database and display real social media post contents on the page.

Next, we include the following code:

const options = {
  root: null,
  threshold: 0.5,
  rootMargin: "100px",
};

const ISObserver = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    newPost();
    ISObserver.unobserve(postContainer.lastElementChild);
    ISObserver.observe(postContainer.lastElementChild);
 }
}, options);

ISObserver.observe(postContainer.lastElementChild);

In the above code, we have created a new IntersectionObserver object with a callback function and an options object. In the function, the isIntersecting property is used to check if the target element (postContainer.lastElementChild) is intersecting with the viewport. If it is, the newPost() function is called to add a new post.

Since the last child element of the postContainer element changes as new posts are added, we call the unobserve() method to stop observing its current last child after it has intersected and then call the observe() method to start observing the new last child.

Now, no matter how far down the user scrolls, new posts will continually be uploaded to the page.

Intersection Observer code example output

Browser Support

The Intersection Observer API is supported in recent versions of most major web browsers. Feel free to check out its complete browser support table.

Mutation Observer

Dynamic changes in the DOM can impact the responsiveness of webpages, making it hard for developers to achieve a perfectly responsive page. The Mutation Observer API provides a way for developers to track the mutation of elements in the DOM. The API was introduced as a replacement for the now-deprecated MutationEvent.

Concept

The MutationObserver(callback) constructor creates a new MutationObserver instance. The constructor takes in a callback function that runs whenever a mutation is detected on the target element. The callback function is passed an array of MutationRecord objects as arguments.

Each MutationRecord object contains information about the target element and the type of mutation that occurred.

MutationRecord object

The observe(target, options) method of the MutationObserver interface begins the observation of a target element and its properties, such as attributes, text contents, and child nodes. The method takes in the following two arguments:

  1. target: A valid DOM element.

  2. options: A required object used to specify what aspects of the target element should be observed for mutations. This is essential because the Mutation Observer is designed to observe every mutation related to the target element. The object must be assigned at least one of the following properties, which by default are set to false:

    • subtree: Set to true to apply all the specified properties in the options object to the descendants of the target element.

    • childList: Set to true to observe the target element and its children if subtree is set to true.

    • characterData: Set to true to observe the text content of the target element and all its descendants if subtree is set to true.

    • characterDataOldValue: Set to true to capture the previous value of the target element’s text content.

    • attributes: Set to true to observe changes in the attributes of the target element and all its descendants if subtree is set to true.

    • attributeFilter: An array used to specify the attributes to be observed in the target element.

    • attributeOldValue: Set to true to capture the previous attribute value of the target element.

Example Usage

To better understand how to make use of the Mutation Observer API, let us take a look at a simple example. In this example, we will be simulating a social media post comment section, where we will utilize the API to observe when a new comment is added to the page, to notify the post owner.

To get started, let’s include the following code in our HTML file to define the structure of our page:

<div id="container">
  <div id="new-comment-alert">
 🟢 A new comment has been posted
  </div>

  <h2>Post</h2>
  <div id="post-content">
    <p>Post contents...</p>
  </div>

  <h2>Comments</h2>
  <div id="comments-container">
    <!-- comments goes here -->
  </div>
</div>;

Next, moving into our JavaScript file, we include the following code:

const commentAlert = document.getElementById("new-comment-alert");
const commentsContainer = document.getElementById("comments-container");

function newComment() {
  let comment = document.createElement("div");
  comment.className = "comment";
  comment.textContent = `This is comment number ${commentsContainer.children.length + 1}`;
  commentsContainer.appendChild(comment);
}

setInterval(() => {
  newComment();
}, 5000);

In the above code, we have defined a function that will be used to add new comments to the comment section in the page. We then called the setInterval() function to upload a new comment every five seconds. This approach helps us simulate the dynamic comment upload in the comment section of a real-world social media application.

Next, we include the following code:

const mutationObserver = new MutationObserver((entries) => {
  if (entries[0].addedNodes.length > 0) {
    commentAlert.style.top = "5px";
    setTimeout(() => {
      commentAlert.style.top = "-50px";
 }, 2000);
 }
});

mutationObserver.observe(commentsContainer, { childList: true });

In the above code, we have created a new MutationObserver instance that listens for the addition of nodes in the commentsContainer element. We then check if the addNodes array property is not empty, which would indicate the addition of a new node inside our target element. If the condition is true, we display the commentAlert element for 2 seconds.

Now anytime a comment is added to the post comment section, a notification pops up on the screen informing the user.

Mutation Observer code example demo

Browser Support

The Mutation Observer API provides a performant way to listen for various mutations in the DOM and is supported by recent versions of most major web browsers. Feel Free to check out the complete browser support table.

Comparing the Three Observers

In this section, we will explore some of the similarities and differences that exist between the three DOM observer APIs.

Similarities

Each of the DOM observer APIs shares some similar features, which you might have realized as we explored each of them individually. Let’s highlight some of their similarities:

  • API Design The constructor of all three DOM observer API is passed a callback function that specifies the actions to perform when an observation is made.

  • Callback arguments: The callback function passed to the constructor of each of the APIs receives the same two arguments: An entries array and the observer itself.

  • Observer methods: The APIs share the same observe() and unobserve() instance methods for starting and ending observations on a target element.

  • Target element: All three DOM observer APIs require a target element to be passed to their observe() method.

Differences

While the three APIs do possess some similarities, they also have several distinctive aspects that set them apart. Let’s take a look at them.

AspectResize ObserverIntersection ObserverMutation Observer
ObjectiveTracks changes in an element’s size and dimensions.Detects the intersection of an element on its target root.listens for mutations in the DOM tree.
options object placementPassed as an opional argument to its observe() methodPassed as an optional argument to its constructorPassed as a required argument to its observe() method
options configuration propertiesboxroot, threshold, rootMarginsubtree, childList, characterDataValue, attributes, characterDataOldValue, attributeOldvalue, attributeFilter
Use casesEnhance elements responsiveness. Update features or content of other elements on resize of the target element.Infinite scrolling. Timing visibility of ads. Lazy loading. Scroll-based animations.Send notifications when elements are added or removed dynamically. Notify screen readers on the dynamic changes in the DOM.

Conclusion

In summary, the DOM observer APIs are powerful and reliable tools that help developers create more adaptable and responsive web applications by allowing them to track and manage changes to the box model, intersection, and mutations of elements in the DOM.

Hopefully, this article has given you a good understanding of these APIs and how you can utilize them in your application. Happy coding!

Gain control over your UX

See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay