by Sarah Okolo
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.
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data.
Happy debugging! Try using OpenReplay today.
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.
-
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”`.
unobserve(target)
: Terminates the observation of the target element being observed.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.
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.
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:
callback
: A required function that specifies the actions to be taken when an intersection is detected. It takes in an array ofIntersectionObserverEntry
objects.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.
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 of1
means 100% interception, and a value of0
means no interception.isIntersecting
: A boolean value that istrue
if any percent of the target element is intercepting its root andfalse
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.
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.
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:
target
: A valid DOM element.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 tofalse
:
* `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.
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()
andunobserve()
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.
Aspect | Resize Observer | Intersection Observer | Mutation Observer |
---|---|---|---|
Objective | Tracks 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 placement |
Passed as an opional argument to its observe() method |
Passed as an optional argument to its constructor | Passed as a required argument to its observe() method |
options configuration properties |
box |
root , threshold , rootMargin
|
subtree , childList , characterDataValue , attributes , characterDataOldValue , attributeOldvalue , attributeFilter
|
Use cases | Enhance 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!
Top comments (0)