Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vanilla JS change active state of links when scrolling refactoring

I'm trying to ditch jQuery from my upcoming projects. I found a way to create a sticky nav on scroll with pure Vanilla JS, and wanted the links on my navigation to change their active state as they reach the corresponding sections.

Below is the working solution I came up with, but I'm sure that the code can be improved to work programmatically without having to select and create conditions for each element.

I'm also using a lot of different selectors, and I'm pretty sure there must be a way to improve that, maybe using querySelectorAll and adding an eventListener in a forEach loop, but I can't get that to work either. Thanks for your help!

// select links
const allLinks = document.querySelectorAll('header nav ul li');
const linkTop = document.querySelector('#linkTop');
const linkAbout = document.querySelector('#linkAbout');
const linkServices = document.querySelector('#linkServices');
const linkClients = document.querySelector('#linkClients');
const linkContact = document.querySelector('#linkContact');
// select sections
const sectionTop = document.querySelector('#top');
const sectionAbout = document.querySelector('#about');
const sectionServices = document.querySelector('#services');
const sectionClients = document.querySelector('#clients');
const sectionContact = document.querySelector('#contact');

function changeLinkState() {
  // home
  if (window.scrollY >= sectionTop.offsetTop) {
    allLinks.forEach(link => {
      link.classList.remove('active');
    });
    linkTop.classList.add('active');
  }
  // about
  if (window.scrollY >= sectionAbout.offsetTop) {
    allLinks.forEach(link => {
      link.classList.remove('active');
    });
    linkAbout.classList.add('active');
  }
  // services
  if (window.scrollY >= sectionServices.offsetTop) {
    allLinks.forEach(link => {
      link.classList.remove('active');
    });
    linkServices.classList.add('active');
  }
  clients
  if (window.scrollY >= sectionClients.offsetTop) {
    allLinks.forEach(link => {
      link.classList.remove('active');
    });
    linkClients.classList.add('active');
  }
  contact
  if (window.scrollY >= sectionContact.offsetTop) {
    allLinks.forEach(link => {
      link.classList.remove('active');
    });
    linkContact.classList.add('active');
  }
}

window.addEventListener('scroll', changeLinkState);
<nav>
  <ul>
    <li id="linkTop">
      <a href="#top">Home</a>
    </li>
    <li id="linkAbout">
      <a href="#about">About Us</a>
    </li>
    <li id="linkServices">
      <a href="#services">Services</a>
    </li>
    <li id="linkClients">
      <a href="#clients">Clients</a>
    </li>
    <li id="linkContact">
      <a href="#contact">Contact</a>
    </li>
  </ul>
</nav>

Thanks a lot!

like image 441
Juan Martín García Avatar asked Nov 27 '25 00:11

Juan Martín García


1 Answers

You can get all sections and links using document.querySelectorAll(). On scroll iterate the list of section from last to first, until you find one that matches. Then remove the .active class from all links, and add it to the link at the active index.

Note: You should use throttling to prevent the calling of changeLinkState multiple times in a second. Another option is to use the Intersection Observer API.

const links = document.querySelectorAll('.links');
const sections = document.querySelectorAll('section');

function changeLinkState() {
  let index = sections.length;

  while(--index && window.scrollY + 50 < sections[index].offsetTop) {}
  
  links.forEach((link) => link.classList.remove('active'));
  links[index].classList.add('active');
}

changeLinkState();
window.addEventListener('scroll', changeLinkState);
nav {
  position: fixed;
  top: 0;
  right: 0;
  width: 10em;
}

section {
  height: 100vh;
  margin: 1em 0;
  background: gold;
}

.active {
  background: silver;
}
<nav>
  <ul>
    <li id="linkTop" class="links">
      <a href="#top">Home</a>
    </li>
    <li id="linkAbout" class="links">
      <a href="#about">About Us</a>
    </li>
    <li id="linkServices" class="links">
      <a href="#services">Services</a>
    </li>
    <li id="linkClients" class="links">
      <a href="#clients">Clients</a>
    </li>
    <li id="linkContact" class="links">
      <a href="#contact">Contact</a>
    </li>
  </ul>
</nav>

<section>
  Home
</section>

<section>
  About Us
</section>

<section>
  Services
</section>

<section>
  Clients
</section>

<section>
  Contact
</section>
like image 178
Ori Drori Avatar answered Nov 28 '25 14:11

Ori Drori