const - JS | lectureAdvanced DOM and Events

Revealing Elements on Scroll

Advanced DOM and Events

In this lecture, we will implement another really cool and modern feature using the Intersection Observer API. We will reveal elements as we scroll close to them. This effect can give your pages a very nice touch ✨, and you can, in fact, easily implement it without any external libraries. Below is a video of the final result.

From the above video, you can see that we basically reveal each section as we approach it. The slide in animation comes from CSS, and so, once more, we will actually achieve this by simply adding a class to each of the sections as we approach them. And we will do that using the Intersection Observer API once again.

Before we start, head over and download the starter files for this lecture, and let's get started.

As we learned from the previous lecture, we will start by creating a new observer.

script.js
const sectionsObserver = new IntersectionObserver();

Next, let's create our callback function. And this time, we will need both the entries and the observer parameters. You will see why in a moment.

script.js
/**
 * Note that these parameters can have any name you want
 *
 * I use these because it's kind of a convention
 */
const sectionsObserverCallback = (entries, observer) => {
  // do something
};

const sectionsObserver = new IntersectionObserver(sectionsObserverCallback);

So, we want to observe all three sections in this case, and it is indeed possible to observe all of them using the same observer. So what we will do is, first, we will select all the sections, and then we will observe them as multiple targets, all using the observer we created.

script.js
// Select all the sections
const allSections = document.querySelectorAll('.section');

// Observe all the sections
allSections.forEach(function(sectionEL) {
    // Observe each section
    sectionsObserver.observe(sectionEL);

    // Hide each section
    sectionEl.classList.add('section--hidden');
}

Let's now create our options object. Our root be the viewport, and our threshold will be a value above 0 since I don't want to show the section right as it enters the viewport. Instead, I want to show it when it is about, say 15% in the viewport. So, let's set our threshold to 0.15.

script.js
const sectionsOptions = {
  root: null,
  threshold: 0.15, // Section will be revealed when 15% of it is visible
};

const sectionsObserver = new IntersectionObserver(
  sectionsObserverCallback,
  sectionsOptions
);

Now that we have the setup in place let's work on the logic of our callback function. Since we only have a single threshold, we will only have a single entry in our entries array. So, we can simply destructure it.

script.js
const sectionsObserverCallback = (entries, observer) => {
  // Destructure the entry
  const [entry] = entries;
  console.log(entry);
};

If we now scroll down until 15% of the first section is intersected, we will see the following in the console even though we can't see the section yet.

Intersection Observer Entry

What's going to be important from the above log will be the target property because now we want to make exactly this section visible, and not all of them. But we are observing all the sections with the same observer. And so now we need a way of knowing which section actually intersected the viewport. Hence, that's what the target property is for.

script.js
const sectionsObserverCallback = (entries, observer) => {
  // Destructure the entry
  const [entry] = entries;

  // Remove hidden class from target
  entry.target.classList.remove('section--hidden');
};

You will notice that the first section happens too soon, whereas the rest actually animate as expected. That's because of the first entry that always gets printed in the beginning as you can see in the above image. If you unroll it, you will see that the target property is the section--1.

Since it is not intersecting, we can actually use that to our advantage. We can simply check if the isIntersecting property is true before we remove the section--hidden class.

script.js
const sectionsObserverCallback = (entries, observer) => {
  // Destructure the entry
  const [entry] = entries;

  if (!entry.isIntersecting) return;
  // Remove hidden class from target
  entry.target.classList.remove('section--hidden');
};

The last thing we need to do now is to unobserve the section once it has been revealed. Because you will notice in the console that as you keep on scrolling, the observer keeps on observing the sections. To do that, we can simply call the unobserve method on the observer and pass in the target.

script.js
const sectionsObserverCallback = (entries, observer) => {
  // Destructure the entry
  const [entry] = entries;

  if (!entry.isIntersecting) return;

  // Remove hidden class from target
  entry.target.classList.remove('section--hidden');

  // Unobserve the target (section)
  observer.unobserve(entry.target);
};

Now, you will notice that once all sections have been revealed, if you keep on scrolling, events are no longer logged in the console. And that's because we have unobserved all the sections. And that's exactly what we want.