Intersection observer for showing active heading in sticky navigation

Viewed 321

I'm not a developer but I'm trying to figure out a way to fix the sticky navigation of my website. My issue is that the style for the active heading is applied as soon as it enters the screen, I would like to activate it when it's on the top of the screen (or when the preceding heading has left the screen).

I followed a tutorial here to implement a dynamic table of contents on my website.

< script >
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      const id = entry.target.getAttribute("id");
      if (entry.isIntersecting) {
        document.querySelectorAll(".active").forEach((z) => {
          z.classList.remove("active")
        });
        document.querySelector(`a[href="#${id}"]`).classList.add("active");
      }
    });
  }); <
/script> <
script >
  document.getElementById("content").querySelectorAll("h2,h3,h4").forEach(function(heading, i) { // runs a function for all headings inside your rich text element
    observer.observe(heading);
    heading.setAttribute("id", "toc-" + i); // gives each heading a unique id
    const item = document.createElement("a"); // creates an anchor element called "item" for each heading
    item.innerHTML = heading.innerHTML; // gives each item the text of the corresponding heading
    ("h2,h3,h4").split(",").forEach(function(x) { // runs a function for each item in your headings list
      if (heading.tagName.toLowerCase() == x) {
        item.classList.add("tocitem", "toc-" + x); // gives each item the correct class
      }
    });
    item.setAttribute("href", "#toc-" + i); // gives each item the correct anchor link
    document.querySelector("#toc").appendChild(item); // places each item inside the Table of Contents div
  }); <
/script>

I understand I’m linking to the heading (non-IT guy trying to decode the code, bear with me).

So if the heading is visible, the intersection ratio is 1 and the active state is selected.

So I need to actually check when the precedent title has left the screen instead of trying to check when the next title is on the screen.

Or maybe I should set everything to active, and when it’s not intersecting, remove the active class.

I've never written a script in my life and I just spent two hours trying out stuff. I tried on the webflow forum to no avail. As I don't think it's a question specific to webflow, I thought I would ask here.

Thank you!

1 Answers

I managed to solve the issue by setting up the root margin in the code.

Here is the code I used:

<script>
const options = {
    rootMargin: '0px 0px -80% 0px'
};
const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    const id = entry.target.getAttribute("id");
    if (entry.isIntersecting) {
      document.querySelectorAll(".active").forEach((z) => {
        z.classList.remove("active")
      });
      document.querySelector(`a[href="#${id}"]`).classList.add("active");
    }
  });
},options);
</script>
<script>
document.getElementById("content").querySelectorAll("h2,h3,h4").forEach(function(heading, i) { // runs a function for all headings inside your rich text element
  observer.observe(heading);
  heading.setAttribute("id", "toc-" + i); // gives each heading a unique id
  const item = document.createElement("a"); // creates an anchor element called "item" for each heading
  item.innerHTML = heading.innerHTML; // gives each item the text of the corresponding heading
  ("h2,h3,h4").split(",").forEach(function(x) { // runs a function for each item in your headings list
    if (heading.tagName.toLowerCase()==x) {
      item.classList.add("tocitem", "toc-" + x); // gives each item the correct class
    }
  });
  item.setAttribute("href", "#toc-" + i); // gives each item the correct anchor link
  document.querySelector("#toc").appendChild(item); // places each item inside the Table of Contents div
});
</script>

That was fun, might end up learning how to code! :D

Related