Achieve dynamic activation of class on scroll using pure Vanilla JavaScript

Exploring vanilla js for the first time. I have a navigation bar with links to different sections on the webpage. My goal is to dynamically add an active class to the corresponding link as soon as its associated section becomes active. If there are no active sections, then the active class should be removed from all links. I came across a helpful script, but it has one drawback. When I scroll to an inactive section, the active class remains with the previous active section.

const links = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('.forJS');
  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);
section{
height:100vh;
scroll-y:auto;
}
.nav-link.active{
  color: red;
}
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cdafa2a2b9beb9bfacbd8df8e3fde3fde0afa8b9acff">[email protected]</a>/dist/js/bootstrap.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7e1c11110a0d0a0c1f0e3e4b504e504e531c1b0a1f4c">[email protected]</a>/dist/css/bootstrap.min.css" rel="stylesheet"/>
<body>
<header class="fixed-top">
  <nav class="navbar navbar-expand-lg navCustom">
    <div class="container">

          <ul class="navbar-nav justify-content-center">
            <li class="nav-item">
              <a class="nav-link" href="#main">Main</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#about">About us</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#portfolio">Portfolio</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#contacts">Contacts</a>
            </li>
          </ul>
    </div>
  </nav>
</header>

<section class="forJS text-center">Some info 1</section>
<section class="forJS text-center">Some info 2</section>
<section class="forJS text-center">Some info 3</section>
<section class="text-center">Some info 4</section>
<section class="text-center">Some info 5</section>
<section class="text-center">Some info 6</section>
<section class="text-center">Some info 7</section>
<section class="text-center">Some info 8</section>
<section class="text-center">Some info 9</section>
<section class="forJS text-center">Some info 10</section>
</body>

P.S.Notes: Due to confusion in the script provided, "changeLinkState()" might need modification without parentheses and the empty condition within the "while" loop needs clarification.

Answer №1

To achieve the desired functionality while making minimal changes to the current design, consider testing the section's height to ensure visibility instead of simply adding the active class to the nearest navigation link as done in the existing code.

if (window.scrollY - sections[index].offsetHeight < 
      sections[index].offsetTop) {
  links[index].classList.add('active');
}

Rather than directly using

links[index].classList.add('active');
, you can adjust the cutoff point with an offset like scrollY + 50. However, hardcoding this number may not be the most ideal solution.

Below is the full updated code:

const links = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('.forJS');

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

  while (--index && window.scrollY + 50 < sections[index].offsetTop) {}

  links.forEach((link) => link.classList.remove('active'));

  if (scrollY - sections[index].offsetHeight <
        sections[index].offsetTop) {
    links[index].classList.add('active');
  }
}

changeLinkState();
window.addEventListener('scroll', changeLinkState);
section {
  height: 100vh;
}

.nav-link.active {
  color: red;
}

section {
  border: 1px solid #555;
}
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d7b5b8b8a3a4a3a5b6a797e2f9e7f9e7fab5b2a3b6e5">[email protected]</a>/dist/js/bootstrap.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ff9d90908b8c8b8d9e8fbfcad1cfd1cfd29d9a8b9ecd">[email protected]</a>/dist/css/bootstrap.min.css" rel="stylesheet" />

<body>
  <header class="fixed-top">
    <nav class="navbar navbar-expand-lg navCustom">
      <div class="container">
        <ul class="navbar-nav justify-content-center">
          <li class="nav-item">
            <a class="nav-link" href="#main">Main</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#about">About us</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#portfolio">Portfolio</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#contacts">Contacts</a>
          </li>
        </ul>
      </div>
    </nav>
  </header>

  <section class="forJS text-center">Some info 1</section>
  <section class="forJS text-center">Some info 2</section>
  <section class="forJS text-center">Some info 3</section>
  <section class="text-center">Some info 4</section>
  <section class="text-center">Some info 5</section>
  <section class="text-center">Some info 6</section>
  <section class="text-center">Some info 7</section>
  <section class="text-center">Some info 8</section>
  <section class="text-center">Some info 9</section>
  <section class="forJS text-center">Some info 10</section>
</body>

Other queries have been addressed in the comments but here are some key points:

  • No parentheses are used on changeLinkState because the function object itself is passed to the callback for later invocation. Using changeLinkState() would pass undefined prematurely and trigger the handler unnecessarily, as explained here.
  • The empty while loop contains merged block logic into the condition shorthand, as detailed here.

Additionally, several design issues remain which I will briefly mention and suggest for further exploration:

  • The bootstrap layout expands the sidebar header across the page, potentially causing unintended overlap between the header and content. Consider restructuring to avoid background occlusion.
  • <section> elements should be within a parent container.
  • Avoid using camelCased CSS properties, and reconsider unclear class names such as forJS.
  • Check for invalid CSS declarations like scroll-y:auto;; use overflow-y: auto; instead.
  • Explore more advanced techniques like throttling and utilization of Intersection Observer for improved performance.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for aligning an element vertically when it has a float using CSS or JavaScript

I am struggling with centering the image within the article list loop I created. The code snippet looks like this: <article class="article <?php if($i%2==0) { echo 'even'; } else { echo 'odd'; } ?>"> <section class ...

When using the ajax method to pass data from the view to the controller, I encountered an issue where the data would unexpectedly become null once it reached the action

function UserLogin() { var username = $("#txtUsername").val(); var passcode = $("#txtPassword").val(); alert(username); $.ajax({ url: '@Url.Action("Login", "UserAccount")', ...

What is the process for updating tabs and their content in React?

Here is where the error occurs in my JavaScript code. I have successfully implemented it in HTML, CSS, and simple JavaScript, but encountered issues when trying to do so in React. My goal is to switch tabs and their corresponding data/content: ...

Choose an item from my unordered list to automatically set the value of a form input

Hello, I am looking for a way to make a long Unordered list always visible on my website. I want users to be able to click on one of the List Items and have that item appear as the value in my Form type=text. I believe using Jquery would be the best way ...

What could be causing this error to appear when using Next.js middleware?

The Issue at Hand Currently, I am in the process of setting up an authentication system using Next.js, Prisma, and NextAuth's Email Provider strategy. My goal is to implement Next.js middleware to redirect any requests that do not have a valid sessio ...

Tips for seamlessly incorporating React Epub Reader into your next js project

Having some trouble integrating the react epub reader with Next Js. It's working perfectly fine with react js, but I keep encountering an error. Can you help me figure out how to integrate the epub reader with next js? Here is the error This is my ...

Using Knockoutjs to fetch and display server-side data within the MVC framework

My goal is to initialize my knockoutjs viewmodel with data from the server. In my ASP.Net MVC project, I achieve this by passing a mvc viewmodel to the view: public ActionResult Edit(int cvId) { CV cv = repository.FindCV(cvId); //auto mapper mapp ...

The issue arises when trying to style a scrollable table in Bootstrap with CSS

I have created a scrollable table using Bootstrap. The scrolling functionality works perfectly, but I am facing an issue with three black lines appearing under the thead tag. I am unsure how to remove them. If you have any suggestions on how to fix this pr ...

Designing CSS outlines to create shapes

I want to design a unique navigation for my website without using images, so I thought using shapes would be a cool idea. Specifically, I am trying to create a crescent moon shape with a border. My goal is to have a border around the red part of the cres ...

Error encountered upon opening an Excel file created using the table2excel jQuery plugin

I am trying to export an HTML table to Excel using the table2excel plugin, but I'm encountering an error in the generated Excel file. How can I resolve this issue? I have set up a JSFiddle demo. To use the plugin, you simply need to follow these ste ...

Hover effect affecting adjacent divs on left and right sides

My navigation bar, known as .navbar, consists of 7 direct child elements called navbar-item. Currently, I have implemented a hover effect where the width and height of a navbar item increase by 20px upon hovering. However, my goal is to enhance this inter ...

Tips for streamlining code using switch statements in vue.js

Is there a more efficient way to simplify this switch statement for handling 5 different cases? Can I streamline the process of updating the completion status based on each step? data() { return { stepOneIsCompleted: false, ...

Click on the paint app to change colors using JavaScript

Trying to create a Paint feature in JS, here's the code snippet. I am looking to change the color of the trail from black to red by clicking on the "red" div, but I'm running out of ideas (without jQuery). let active = false; const draw = fu ...

Challenges with rendering text in Three.js

We are currently working on a project in three.js and facing difficulties when it comes to loading fonts onto our text elements. Our approach involves using the TextGeometry object for rendering fonts and the typeface js converter to incorporate new fonts ...

Preserving Selected Option in a Dropdown After AJAX Call in a Partial View

Within my ASP.NET Core web application, I am faced with a scenario where a partial view needs to be integrated into multiple views. This partial view must be able to adapt to dynamic data based on the specific view that triggers its rendering. The area hig ...

What are some ways I can incorporate 'watch' into my npm scripts?

Here is the current layout of my directory: This is what my package.json file contains: { "name": "personal_site", "version": "1.0.0", "description": "My personal website.", "main": "index.js", "scripts": { "test": "echo \"Error: no te ...

Is there a powerful alternative to ThreeJS that can be integrated into Android apps?

I am in the process of creating a website that allows users to personalize their jewelry online. To achieve this, I am utilizing ThreeJS. My next step is to develop an Android app, which requires me to create an API-only app for both the website and the An ...

I'm puzzled as to why the banner text for my three images in the slider is only displaying on one of the images, all crammed together

Currently, I am working on an ecommerce project with Next.js. One of the challenges I faced was while setting up my banner page that includes a react-slick slider for images. Initially, when I added just one image, I noticed multiple renderings of it, but ...

Form an item using an array

Is there a way to efficiently convert this array into a map? Here is how the array looks: var array = [{ "id" : 123 }, { "id" : 456 }, { "id" : 789 }]; The desired output should be: var result = { "123": { id: 123 } , "456": { id: 456 } , ...

Form design featuring inline radio buttons and text fields arranged horizontally

I am struggling to arrange a radio button with two text fields in a horizontal form using Bootstrap. Unfortunately, I can't embed the image for reference. Despite my efforts in using Jade markup, I haven't been able to achieve the desired layout ...