Using Intersection Observer API to make button styling contextual.
Tags: javascript
Published: 2020-11-07 14:39:22

---
title: Using Intersection Observer API to make button styling contextual.
published: true
date: 2020-10-29 00:00:00 UTC
tags: javascript
canonical_url: https://sourabhdaroji.com/feed.xml
---

> Intersection Observer is like a sniper rifle, and the browser is the hitman. You need to tell the hitman to look at a particular building and use his rifle to take actions on the _targets_.

We all love the minute but highly engaging feedback we get when we do small things like liking a tweet, typing a wrong pass code on iOS, or for the Josh Comeau fans out there, [liking his blog post](https://joshwcomeau.com/react/serverless-hit-counter/ "Josh's Blog on CSS Full Bleed"). These microinteractions make the experience of using an interface much more appreciable.

![GIF of Twitter's Like Button](https://sourabhdaroji.com/images/twitter_mi.gif "Twitter's Like Button") ![GIF of Josh Comeau's like button](https://sourabhdaroji.com/images/jc_mi.gif "Josh Comeau's like button")

#### Why the contextual button?

I wanted to implement a microinteraction on my website. Particularly, on the project viewing page. Not without a reason though. I wanted to have a way for the visitors to skip to the UX Sections of the projects. The reason being that, this website serves me with two purposes, it acts like my blog and my portfolio. So whenever I apply for a UX Job, if the recruiter happens to be viewing my projects, which mostly contain both technical and UX Process involved in the project, the recruiter may get confused seeing the technical information being presented and may get misled to think that I am only a tech person. To avoid such a situation, I wanted to give a heads up that UX section is down in the project and you can either skip to that section by pressing a button or read the whole content.

![GIF of Envisioned Context Aware Skip to UX Section Button](https://sourabhdaroji.com/images/context-button.gif "Envisioned Context Aware Skip to UX Section Button")

> Why didn't I just re-organise the project structure?

Well, I had already drafted multiple projects in this way and for every project, I usually follow the same structure. So, this option was not feasible for me.

For the design, I iterated it twice:

**Having a sticky bottom bar that says directly "Skip to UX Section"**

This was functionally good but didn't give a good feedback for the viewers. It was also taking up more space, since I could add much needed features like back-to-top button and share button in that space if I use icons instead of plain text.

![Image for Gen 1 design for skipping to UX](https://sourabhdaroji.com/images/old-mi.png "Gen 1 design for skipping to UX")

**Having a sticky bottom bar that has relevant icons instead of plain text**

Here the "Skip to UX" button is no longer a text, but an icon, that pulsates when not in the UX Section and becomes static when in the section.

![Image for Final design for skipping to UX](https://sourabhdaroji.com/images/icons-mi.png "Final design for skipping to UX")

Also, it has a little downward pointing arrow in the icon to convey that the UX Section is coming up, below and the arrow disappears when you enter the UX Section.

![Image for The button state change for when the viewport is in the UX Section](https://sourabhdaroji.com/images/icon-change.png "The button state change for when the viewport is in the UX Section")

#### The JavaScript

```
    let options = { rootMargin: '0px', threshold: 0 }; let callback = (entries, observer) => { entries.forEach(entry => { if (entry.boundingClientRect.y <= 1) { // inside ux section document .querySelector('#skip-to-ux') .classList .replace('s2ux-out', 's2ux-in'); } else { // outside ux section document .querySelector('#skip-to-ux') .classList .replace('s2ux-in', 's2ux-out'); } }); }; let observer = new IntersectionObserver(callback, options); let target = document.querySelector('#heading-the-ux-process'); observer.observe(target);
```

**Intersection Observer API according to MDN:**

> The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's [viewport](https://developer.mozilla.org/en-US/docs/Glossary/viewport).

To simplify and put it in the context of this article, Intersection Observer API, provides the browser a way to track where exactly in the page is the viewer with respect to the viewport, so that it can decide on what version of the UX Button can be shown to the user, based on our logic.

The Intersection Observer `constructor` function takes two arguments a `callback` function and an `options` object.

```
    let observer = new IntersectionObserver(callback, options);
```

Once the Intersection Observer object is created, it must be told to observe certain element(s) in the DOM. This is done using the `observe` method on the Intersection Observer object that was created. You can observe multiple DOM elements by calling observe method with different target elements.

```
    let target = document.querySelector('#heading-the-ux-process'); observer.observe(target); // observer.observe(target2); // and so on.
```

The **callback function** is where we write the logic on what should happen when the observer "sees" the target element or when it doesn't "see" the target element. In this example, I am swapping css classes based on if the element is visible or not.

```
    let callback = (entries, observer) => { entries.forEach(entry => { if (entry.boundingClientRect.y <= 1) { // inside ux section document .querySelector('#skip-to-ux') .classList .replace('s2ux-out', 's2ux-in'); } else { // outside ux section document .querySelector('#skip-to-ux') .classList .replace('s2ux-in', 's2ux-out'); } }); };
```

In the callback function you can see that it gets two parameters passed by the observer:

- The `entries` parameter is a list of entries corresponding to each target element for which the observer registered a change in its intersection status.
- The second parameter is the `observer` itself. This `observer` object can be used for example when you don't want to observe a target element anymore once the callback is called. To do that you would call the `unobserve` method along with the target to "unobserve" as the parameter.

The **options object** helps you customize the observable area that the observer needs to observe. The options object can have the following properties as mentioned by MDN:

- **root:** The element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if `null`.
- **rootMargin:** Margin around the root. Can have values similar to the CSS [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "The margin CSS property sets the margin area on all four sides of an element. It is a shorthand for margin-top, margin-right, margin-bottom, and margin-left.") property, e.g. "`10px 20px 30px 40px"` (top, right, bottom, left). Defaults to 0.
- **threshold:** Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback to run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. The default is 0.

```
    let options = { rootMargin: '0px', threshold: 0 };
```

The above object would give us an observer, that watches the viewport of the browser, with no margins and 0 threshold, which means when a single pixel of the target element is visible, the callback is triggered.

After implementing this in my projects page, here is how it looked in action:

![GIF of Project Page after implementing Intersection Observer](https://sourabhdaroji.com/images/peek-2020-10-29-00-44.gif "Project Page after implementing Intersection Observer")