Revealing Multiple Content on Scroll Using JavaScript's Intersection Observer
Tags: javascript, html, css, animation
Published: 2021-05-27 18:01:05

In this article, we would continue our discussion on JavaScript's Intersection Observer API and how we could easily use it to observe multiple elements in the DOM, revealing each as they come into view. As this is the second part of a two-series article, it is *mandatory* that you've read the first part of the Intersection Observer series titled [*Revealing Contents on Scroll Using JavaScript’s Intersection Observer API*](https://dev.to/daveyhert/a-beginner-s-guide-to-the-intersection-observer-api-2d50). It is the foundation from which this article is built on and the core essential basics of the Intersection Observer has been thoroughly covered in that article. 

## Prerequisites

- Basic knowledge of JavaScript (beginner level is perfectly fine as I'd explain everything in great details like I was explaining to a 5-year-old. :)
- Basic knowledge of HTML and CSS (you've built at least one basic webpage with them).
- A code editor (VS Code recommended).
- A browser of course (Chrome or Firefox recommended).
- You've read the first article of the series, or at least have a basic understanding of the Intersection Observer. 👇
{% link https://dev.to/daveyhert/a-beginner-s-guide-to-the-intersection-observer-api-2d50 %}


## Creating a Basic HTML/CSS Page
As with the previous article, we’ll start by creating a simple HTML page with a hero header section and a title inside of it. Then below the header, we'll create 3 sections and for each section, we will create a title and a content box with two columns inside it. Each column would have an image inside (one in the left column and the other in the right column). The idea is as we approach each section, the columns would slide in with the image in them from their respective sides i.e the one on the left would come in from the left side of the viewport, while the one on the right would come in from the right. So let's dive into the HTML.

```
  <body>
    <!-- Header -->
    <header>
      <h1>Just a Header</h1>
      <h3>Scroll to Reveal Content</h3>
    </header>

    <!--Section 1 -->
    <section class="section-1">
      <h2>Section 1</h2>
      <div class="content">
        <div class="left-column">
          <img class="img" src="img/image1.jpg"/>
        </div>
        <div class="right-column">
          <img class="img" src="img/image2.jpg"/>
        </div>
      </div>
    </section>

    <!--Section 2 -->
    <section class="section-2">
      <h2>Section 2</h2>
      <div class="content">
        <div class="left-column">
          <img class="img" src="img/image3.jpg"/>
        </div>
        <div class="right-column">
          <img class="img" src="img/image4.jpg"/>
        </div>
      </div>
    </section>

    <!-- Section 2 -->
    <section class="section-3">
      <h2>Section 3</h2>
      <div class="content">
        <div class="left-column">
          <img class="img" src="img/image5.jpg"/>
        </div>
        <div class="right-column">
          <img class="img" src="img/image6.jpg"/>
        </div>
      </div>
    </section>
  </body>
``` 
Now for the CSS, we'll give the header and each section a height of `100vh`, align the title text to the `center`, then set the content box to a 2-column view (each column taking `50%` of the viewport's `width`) and vertically centring everything using `flexbox`. We'll then set each image to a `block` element, give the images a fixed responsive `width` and then using `margin:auto` to horizontally centre each in their respective column.  Now to add a bit of aesthetics to our design, we'll apply a gradient background colour to each section to separate them, import a decent font from Google font and then add a bit of polaroid depth to the images using `box-shadow`. Lastly, we will create two hidden classes (`hidden-left` and `hidden-right`) that would be responsible for hiding and revealing each content's column later on using JavaScript and apply a transition effect on each column.

```
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;800&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

h1, h2, h3 {
  text-align: center;
  margin-bottom: 3rem;
  color: #222;
  text-align: center;
  font-family: "Nunito", sans-serif;
}

h1 {
  font-size: 4rem;
  font-weight: 800;
}

h2 {
  margin-bottom: 5rem;
  font-size: 3rem;
}

h3 { font-size: 2rem;}

.img {
  width: 95%;
  max-width: 550px;
  display: block;
  margin: 0 auto;
  border-radius: 6px;
  box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.5);
}

section, header {
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: #fdfbfb;
  background-image: linear-gradient(120deg, #fdfbfb 0%, #ebedee 100%);
  overflow: hidden;
}

.section-2, header {
  background: #ebedee;
  background-image: linear-gradient(135deg, #f5f7fa 0%, #cfd4dd 100%);
}

.content {
  display: flex;
}

.content > div { 
  width: 50%;
}

.hidden-left {
  opacity: 0;
  transform: translateX(-100%);
}

.hidden-right {
  opacity: 0;
  transform: translateX(100%);
}

.left-column {
  transition: all 1.5s ease-in-out;
}
.right-column {
  transition: all 1.5s ease-in-out;
}
```
The resulting layout👇

![Layout Output 2.gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vvw5fjubcs273uvklp2b.gif)

Great, we have our basic webpage set up, now let's dive into JavaScript and talk about how we can observe each section using the Intersection Observer.

## Implementing the Intersection Observer API on Multiple Elements

Recall from the previous article that the steps for observing an element are as follows:

- Select the target element you wish to observe `section`. 
- Define an `options` object with your customisation options for the Intersection.
- Create the actual `Observer` using its object constructor function and pass it a callback function and the options object `new IntersectionObserver(callback, options)`.
- Pass the target element to be observed to the `Observer` using the observe method `Observer.observe(section)`.
- Finally, define the `callback` function with the desired Intersection behaviour.

That's it! If you can vividly remember these steps above then we can begin our implementation of the observer on multiple sections, but if you can't recall exactly what we did previously, you're advised to quickly go read up the  [part one](https://dev.to/daveyhert/a-beginner-s-guide-to-the-intersection-observer-api-2d50) before continuing past here. 

**Note:** The Intersection Observer can accept only one element for observation at a time. This means if you need to observe multiple elements you'd have to iterate (loop) over all of them and observe each of them separately:

Now let's begin, we'll start by selecting all 3 sections that we wish to observe.
```
const allSections = document.querySelectorAll("section");
```
This selector returns a Nodelist containing all 3 sections in our webpage

![new nodelist.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gf26v814quka7momjefl.png)

**Note**: A NodeList is simply a collection of elements in the DOM that is stored in what looks like an array, and although it isn't an array, we can loop through a NodeList like actual arrays.

Next, we will define the `options` object needed to customize the Intersection. We will set the `root` element as the browser `null`, then set the `threshold` to `0.3` i.e the callback should be fired when 30% of the target section comes into view. We won't specify any `rootMargin` as we have no need for it, we only used it previously to discuss its use case.
```
const options = {
    root: null,
    threshold: 0.3,
  }
```
With that out of the way, we can finally create an observer using its default constructor function and then pass it a callback function and the `options` object. 
```
 const sectionObserver = new IntersectionObserver(callback, options);
```
Finally, we have to tell the `sectionObserver` what it should observe in the DOM by passing the element to the observer. But since we are interested in multiple elements that we previously stored in a NodeList, we simply have to loop through this NodeList and for each iteration (loop), we pass each section element to the observer. By the end of the loop, all 3 sections would be under observation.
```
allSections.forEach(section => {
  sectionObserver.observe(section);
});
```
Let's go through what we just did. We first selected the target sections to be observed and stored them in a NodeList `allSection`, then we created an actual observer `sectionObserver`, and finally we told the observer what to observe by looping through the `allSection` NodeList using a `forEach()` method and then we passed each target section to be observed into the observer using the `observe()` method. That's it, we have everything set up, the only thing left now is defining our callback function to be executed.

#### Important Detail
Recall from the previous article that the observer always fires the callback function once when our webpage loads for the first time? Well, that "once" only applies to when you are observing a single element, but when you're observing multiple elements the callback gets fired once for each element under observation. This does not mean there is an actual Intersection, but the elements are simply getting registered by the observer. If we consoled log the `entries` in our callback, we'd get the array of each element's entry that was fired.

![default behaviour.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f1ju7a4xfpqsm66kzvou.png)
This can be very confusing for most beginners and trigger the wrong behaviour in their code if not properly guarded or written.

Now that we have the observer set up, let's hide all the content (columns) that we wish to reveal on Intersection by adding the hidden classes to them and then writing a callback function that removes these hidden classes on Intersection. We could have simply added the classes manually in our HTML, but that's bad practice as a user's browser might have JavaScript turned off, which would result in our webpage not displaying any content as JavaScript wouldn't be able to remove the hidden classes.
```
document.querySelectorAll(".left-column").forEach(column => {
  column.classList.add("hidden-left");
})
document.querySelectorAll(".right-column").forEach(column => {
  column.classList.add("hidden-right");
})
```
What did we just do? We simply selected all the columns on the left and then looped through them using `forEach()` and then added the `hidden-left` class to each column. Then we did the same exact thing for the right columns using the `hidden-right` class.


## The Callback Function

We can now define the callback function, but before getting into that I'd like to talk about something I did not get to cover in the previous article and that's how to un-observe (stop observing) a target element after its Intersection. Now you might be wondering why you'd want to stop observing a target element after an Intersection? Well, let's take a cursory look at the behaviour of the webpage from the previous article.  

![previous layout.gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x911pcjdgyqoq3x5hxjb.gif)

Notice that the image content keeps sliding in as it comes into view and then sliding back out as we scroll out of view, which means the callback function keeps firing over and over again for as long as the user keeps scrolling on the webpage. We might not always want this behaviour as we might simply want the content to reveal itself once it comes into view and nothing more (it should remain visible even if we scroll out of view).

For this, the observer has an `.unobserve()` method that can be used to un-observe a target element, but to be able to use this method, we'd have to pass the actual observer that is firing the callback function to this same callback function as a second argument. Recall that the callback function by default receives an `entries` array when the callback function is fired by the Observer? Well, that's not the only thing that the callback function can receive, the observer itself can be passed to it as a second argument so we can un-observe an element with it. With that in mind, let's create our callback function before going over it line by line.

```
function callback(entries, observer) {
  const [entry] = entries;

  if (!entry.isIntersecting) return;

  //   get the class name of the target section that came into view
  const curSectionsName = entry.target.getAttribute("class");

  //   now target that specific section
  const curSection = document.querySelector(`.${curSectionsName}`);

 //   remove the hidden classes (DOM traversing)
  curSection.lastElementChild.firstElementChild.classList.remove("hidden-left");
  curSection.lastElementChild.lastElementChild.classList.remove("hidden-right");

  observer.unobserve(entry.target);
}
```

Now let's dissect the callback function line by line.

- `const [entry] = entries`;  
Again, recall that the Observer passes an entries array to the callback containing an IntersectionObserverEntry object? We simply deconstructed (extracted the object in) the array and stored it in an ```entry``` variable to make it easier to directly access the properties stored in that object.

- `if (!entry.isIntersecting) return`; 
This line of code is considered a guard. Because the callback is fired for each observed section by default, we only want to do something (run the code below this line) when there is an actual intersection (i.e when `isIntersecting` is `true`), we are currently checking to see if there is a `false` Intersection and if that is the case, we want the function to terminate itself (to simply return without running any of the code below this line of code). Consider it a body-guard if you would. <br/><br/>

- `const curSectionsName = entry.target.getAttribute("class")`;
We simply check the `target` property in the `entry` object for the exact section that came into view (that has intersected with the viewport), then we take its class name and store it in a variable `curSectionsName`. <br/><br/>

- `const curSection = document.querySelector(`.${curSectionsName}`)`; Because we previously only fetched the class name of the section that came into view, this line simply targets that exact section in the DOM using the query selector and then we passed the current sections class name stored in `curSectionsName` to the selector (we used template strings to pass that variable into our selector). Then we stored the current section in `curSection`. <br/><br/>

- `curSection.lastElementChild.firstElementChild.classList.remove("hidden-left")`;
Now that we have the section in view, we do not want to do anything with the section itself, but the two columns inside it. But do you recall from our HTML that we put both columns in a `content` container? So we have to traverse the DOM (move around in the DOM) to grab them. Let's take a quick look at the logic (for beginners). <br/><br/>

    - `curSection`: Recall that this is the current section in the viewport.
    - `.lastElementChild`: Because each section has a `h2` text and a `content` container as children, we simply target the last child i.e the `content` element.
    - `.firstElementChild`: Now that we have access to the `content` container holding both columns, we use this line selector to targets the first column `left-column` in the content element as it is the first element in the container.
    - `.classList.remove("hidden-left")`: Then we simply check the classList of this column and remove the hidden class we previously added to it. 
That's it, we simply repeat the exact same thing for the right column in the next line of code. <br/><br/>

- `observer.unobserve(entry.target)`: Recall that while defining the callback function, we passed the observer firing this same function as a second argument to the function itself? As a result our callback now has access to the Observer firing it. We simply tell the observer to stop observing the target section that came into view after its content has been revealed.

That's all, our webpage should now behave as expected.

![Final layout 3.gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qogb0f7wqchteznnich4.gif)

As you can see, once we scroll towards each observed section, once 30% of the section comes into view in our viewport an Intersection is triggered and the callback is fired. The hidden classes get removed and the columns are revealed as they slide back into their original position. Then the Observer stops observing that section.

-  [Demo](https://revealcontentsonscroll.netlify.app/) 
-  [Source Code](https://github.com/DaveyHert/Multiple-Intersection-Observer) 


## Conclusion

Congrats!!!🎉 You have successfully learnt how to observe multiple elements in the DOM using the Intersection Observer, how to reveal multiple contents while scrolling the webpage and how to un-observe an element after an Intersection.

#### Buy me a coffee☕
If you liked my article and found it helpful, you can buy me a coffee using any of the links below.👇

- [Ko-fi](https://ko-fi.com/daveyhert)
- [PayPal](http://paypal.me/DaveyHert)
- [Paytreon](https://www.patreon.com/DaveyHert)
- [Buy Me a Coffee](https://www.buymeacoffee.com/DaveyHert)

And also do well to leave a reaction and follow me for more awesome contents. And if you've got any questions or spotted any errors, please do well to leave some feedback. Thank you for reading.

## References
-  [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) 
-  [W3.Org](https://www.w3.org/TR/intersection-observer/)