Discovering Observer Web APIs
Tags: webdev, javascript
Published: 2021-04-03 08:52:52

When running in a browser environment, you can use many Web APIs in your JavaScript code. From simple stuff like accessing the DOM, through [integrated payments](https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API), [vibrations](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API), [cryptography](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API), [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API), all the way to infinity & beyond.

Today, we’ll be exploring a small section of Web APIs, something that one could call *“observer-based Web APIs”*. These are:

-   Mutation Observer API
-   Resize Observer API
-   Intersection Observer API

So, seemingly unrelated Web APIs, with different use-cases, but still, having one thing in common - the observer-based architecture. This means that they’ll share similar API structures, and in general, allow you to observe and react to certain changes and values.

With this little introduction, let’s jump right into it.

# Mutation Observer API

Starting with arguably the oldest and [most well-supported of the bunch](https://caniuse.com/mdn-api_mutationobserver) (it even works on IE 11), we’ve got the **[Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)**.

Consisting solely of `MutationObserver` interface, it allows you to watch for changes, aka **mutations** to the **DOM**. You can watch for changes like an addition/removal of child nodes, alternation of character data, and augmentations of attributes. All that for only the target element or its **entire subtree**.

## How does it work?

Usage of the `MutationObserver` is fairly simple. Just initiate it with a callback function and then use the `observe()` and `disconnect()` methods of the created instance, to respectively watch a DOM node for changes and stop the entire instance from any active watches.
```javascript
// Example target element.
const target = document.getElementById("target");
/* Callback recieving the observer instance, 
   and a slew of mutations' info about a change that triggered it.
*/
const mutationObserver = new MutationObserver((mutations, observer) => {
  for (const mutation of mutations) {
    if (mutation.type === "childList") {
      // Child tree change.
      mutation.addedNodes; // Added DOM nodes.
      mutation.removedNodes; // Removed nodes.
      mutation.target; // The parent (relevant when watching the whole subtree)
    } else if (mutation.type === "attributes") {
      // Attribute change.
      mutation.attributeName; // Name of the changed attribute.
      mutation.oldValue; // Previous value of the attribute (if enabled in options)
    }
    /* For more mutation info see:
       https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord
    */
  }
});

// Observe the target DOM node for the selected changes.
mutationObserver.observe(target, {
  /* For more options see: 
     https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
  */
  attributes: true,
  childList: true,
  subtree: true,
});
// When no further observing is required.
mutationObserver.disconnect();
```
Apart from the two mentioned methods, there’s also the `takeRecords()` method, which returns an array of `MutationRecord`s (the same one from the callback function) that were detected but not yet processed through the callback. It’s useful for doing final processing before `disconnect()`.
```javascript
// ...
const mutations = resizeObserverr.takeRecords();

mutationObserver.disconnect();

if (mutations) {
  // Run one, final callback.
  callback(mutations);
}
```
## How is it useful?

`MutationObserver` can be used in many different ways. Especially when running as a 3rd-party script on a foreign website, it allows you to **react to DOM changes** as they happen.

On the other hand, when you’re creating a website from the ground up and have the knowledge and control over the DOM changes, `MutationObserver` can still be useful for watching when, e.g., when a 3rd-party widget or a specific part of it gets loaded.

`MutationObserver` is much faster, cleaner, and easier to use than running change-checking intervals. Still, observing nodes such as whole `<body>` with its entire subtree will definitely cause performance issues.

With this little overview of `MutationObserver`, you should have a pretty good understanding of how these observer-based Web APIs work and look like, as, like I’ve said, most of them have a similar structure.

Having said that, let’s explore another API!

# Resize Observer API

**[Resize Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)** consists, like the previous API, of a single `ResizeObserver` interface, an instance of which has 3 methods - the base `observe()` and `disconnect()`, but also `unobserve()`.

Now, Resize Observer API allows you to observe for **resize changes** of **any DOM element**. This means that you can not only watch for window resizes more efficiently than with the `window.onresize` event, or more accurately than with `@media` rules, but also that you can actually react to changes on an element-basis when, e.g., the user uses the resize handle, or layout update happens.

This is a very useful feature for many applications. You’ll no longer have to set intervals and use the costly `getBoundingClientRect()` method just to react to an element’s size change. However, it is pretty new and available only on [newer versions of the evergreen browsers](https://caniuse.com/mdn-api_resizeobserver).

As for how you can use it:
```javascript
// ...
/* Callback recieving the observer instance, 
   and a slew of resize entries for observed elements.
*/
const resizeObserver = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // The resized element's DOMRect - contains size and position info.
    entry.contentRect;
    entry.contentRect.width;
    entry.contentRect.height;
    entry.contentRect.x;
    entry.contentRect.y;
    /* For more resize entry info see:
       https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry
    */
  }
});

resizeObserver.observe(target);
// When no further observing is required
resizeObserver.disconnect();
```
Now, `ResizeObserver` in comparison to `MutationObserver` doesn’t have the `takeRecords()` method, and so it doesn’t queue the incoming changes.

To compensate for that, there’s an `unobserve()` method. It’s similar to `disconnect()` but instead of clearing the whole observer instance, it only “unobserves” the provided element, allowing the same observer to easier manages multiple elements.
```javascript
// ...

observer.unobserve(target);
```
# Intersection Observer API

Last but not least, we’ve got the **[Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver)**. It can be used to observe **intersections between parent and child elements** (usually between any DOM element and root viewport). That’s useful for detecting, e.g., approximate scroll position (by placing a dummy element at the scroll position of interest), whether the user has displayed an ad or other widget, whether we should load more content in an infinite scroller, etc.

Browser support-wise, it [sits between the previous 2 APIs](https://caniuse.com/mdn-api_intersectionobserver), being supported by older versions of evergreen browsers than `ResizeObserver`, but still not by IE, like `MutationObserver` is.

Now, as for the actual API, it feels like a combination of the 2 prior ones. You’ve got a single interface - `IntersectionObserver`, an instance of which has all 4 previously-introduced methods, including both `takeRecords()` and `unobserve()`, all of which serve a similar purpose to their counterparts in both `ResizeObserver` and `MutationObserver`.

Just like with `MutationObserver`, `IntersectionObserver` also accepts an options object, but this time directly in the constructor call, right after the callback. All of the set options, or their defaults, can be later accessed directly through the observer’s instance.
```javascript
// ...
/* Callback receiving the observer instance,
   and a slew of intersection entries for observed elements.
*/
const intersectionObserver = new IntersectionObserver(
  (entries) => {
    for (const entry of entries) {
      entry.isIntersecting; // If the element is intersecting with the root.
      entry.intersectionRatio; // Ratio of intersection.

      /* For more intersection entry info see:
        https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry
      */
    }
  },
  {
    /* For options see:
       https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver
    */
  }
);
intersectionObserver.observe(target);
// To unobserve given element.
intersectionObserver.unobserve(target);
// Take last entries before disconnecting.
const entries = intersectionObserver.takeRecords();
// Disconnect observer completely.
intersectionObserver.disconnect();
```
# There’s more

Now, apart from the DOM-related Observers we’ve just covered, there’s also the `PerformanceObserver` ([Performance Observer API](v)) and `ReportingObserver` ([Reporting Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ReportingObserver)) - both having APIs similar to `MutationObserver` (`observe()`, `disconnect()`, `takeRecords()`) and can be used to observe performance measurements, and reports respectively.

Both of these observers are, in general, less used than the 3 listed and `ReportingObserver` is even a part of the [Reporting API](https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API), which’s **experimental** and **Chromium-exclusive** right now.

With that said, I hope this post has given you a better understanding and possibly even reassurance to try out and use at least one of the Web API observers in your projects. They’re all uniquely useful and can provide you with clean interfaces to functionalities that were either hard or impossible to achieve in the past.

For more web development content, covering frameworks, Web APIs, and more, **follow me** on [Twitter](https://twitter.com/areknawo), [Facebook](https://www.facebook.com/areknawoblog), or through [my newsletter](https://areknawo.com#newsletter). Thanks for reading, and happy coding!

> This post was written with ease, made grammatically correct, and cross-posted here within 1 click thanks to [**CodeWrite**](https://codewrite.io) with its great editor, smooth Grammarly integration, and "one-click publishing". Try it for free, and use the code `first100` to get **20% off** your subscription (only **$2.40/month**, or **$24/year**!)