Intersection Observer

What is it?

At some point in your journey you'll want to track where abouts on a webpage a person might be.

While you can definitely do this with an onscroll event listener, this could fire thousands of events per person per time they visit your site which doesn't sound super efficient right?

Enter IntersectionObserver API:

According to MDN "When an IntersectionObserver is created, it's configured to watch for given ratios of visibility within the root. "

Where "the root" is the viewport, so that is: how much of a section is visible or not visible.

So while creating this I ran into a bit of bother. I could either get it to work on a Laptop, but not mobile, or mobile but not laptop.

Which was frustrating, and I couldn't figure out why.

const options = {
  threshold: 1,
};

Using this example: this would work on laptop like a treat, but not on mobile.

This would work on mobile but not on laptop.

const options = {
  threshold: 0.2,
};

But why?

So after staring at my screen for a while it dawned on me.

So a threshold of 1 would mean that 100% of the section would need to be visible on the screen for it to be considered "intersecting"

Which is exactly what it is I was looking for. But why wouldn't it work on mobile?

Well the answer is in the simplest of behavior we all know and love.

The smaller a screen gets the taller the content gets, which would mean that on mobile devices, 100% of the section would never be visible. and so the navigation bar would not update.

The solution

So my first solution was to measure the viewport width, like a media query. But at what point is a phone a phone?

So then I decided to change my approach to the below: If any section height is bigger than the innerheight of the window only some of the section needs to be visible.

Otherwise all the section needs to be visible.

function setThreshold() {
  // can the whole section fit in the viewPort?
  let height = true;
  allSections.forEach((section) => {
    if (section.getBoundingClientRect().height > window.innerHeight) {
      return (height = false);
    }
  });
  if (height) {
    return 1;
  } else {
    return 0.2;
  }
}


Other Resources:

Mozilla Intersection Observer Documetation

Kevin Powell Intersection Observer YouTube Video

Full JavaScript code is available below:

let allLinks = document.querySelectorAll(".nav-link");
let allSections = document.querySelectorAll("section");

function setThreshold() {
  // can the whole section fit in the viewPort?
  let height = true;
  allSections.forEach((section) => {
    if (section.getBoundingClientRect().height > window.innerHeight) {
      return (height = false);
    }
  });
  if (height) {
    return 1;
  } else {
    return 0.2;
  }
}

const updateNavBar = (entries, observer) => {
  entries.forEach((entry, index) => {
    if (entry.isIntersecting && entry.intersectionRatio >= options.threshold) {
      allSections.forEach((section, sectionIndex) => {
        if (section.getAttribute("id") === entry.target.getAttribute("id")) {
          allLinks.forEach((link, linkIndex) => {
            link.classList.remove("active");
            if (sectionIndex === linkIndex) {
              link.classList.add("active");
            }
          });
        }
      });
    }
  });
};

const options = {
  threshold: setThreshold(),
};

const observer = new IntersectionObserver(updateNavBar, options);

allSections.forEach((section) => {
  observer.observe(section);
});

// this is for smooth scroll if not using smooth scroll you can omit this
allLinks.forEach((link) => {
  link.addEventListener("click", (e) => {
    for (let index = 0; index < allSections.length; index++) {
      observer.unobserve(allSections[index]);
      allLinks[index].classList.remove("active");
    }
    window.addEventListener("scrollend", (e) => {
      allSections.forEach((section) => {
        observer.observe(section);
      });
    });
  });
});

JavaScript
Avatar for Shane-Donlon

Written by Shane-Donlon

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.