最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Vanilla JS change active state of links when scrolling refactoring - Stack Overflow

programmeradmin5浏览0评论

I'm trying to ditch jQuery from my uping 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>

I'm trying to ditch jQuery from my uping 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!

Share Improve this question edited Aug 26, 2018 at 11:37 Federico Grandi 6,8065 gold badges33 silver badges51 bronze badges asked Aug 26, 2018 at 11:05 Juan Martín GarcíaJuan Martín García 812 silver badges8 bronze badges 0
Add a ment  | 

2 Answers 2

Reset to default 17

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>

There can be more than one ways of highlighting the Nav. One way of implementing this feature is as follow-

We will apply "active" class to an active Nav link and add some css for this class to highlight it.

Here are the steps how you can apply "active" class to a proper Nav link:

STEP 1-

Add a class to every Nav links same as the id of corresponding section while creating your Nav bar.

For example, the Nav link corresponding to a section having id="section1" will have its class set to section1 ( i.e class="section1")

STEP 2-

On Scroll event in the browser, iterate through every section and check if it is in the view-port (getBoundingClientRect()) .

If a section is in the viewport, make this as well as the corresponding Nav link active as follow-

if (section is in the viewport) {
    // 1. Add "your-active-class" to the current section
    // 2. Add "active" class to the Nav link which have a class same as id of the current section
    } else {
     // 1. Remove "your-active-class" from the current section.
 // 2. Remove "active" class from the Nav link which have a class same as id of current section
    }

STEP 3-

In you css file, write styles for class "active" and "your-active-class" so that the Nav link and section can be highlighted using different text or background colors.

发布评论

评论列表(0)

  1. 暂无评论