Make the Change of "Active" Class in the Menu More Reliable When Scrolling

Change navigation active class on window scroll

DEMO

Serlite's code is very good but had some bugs.

  1. If you scroll down to the end last two a elements have active class so both are highlighted.

Solution

added $('#menu-center ul li a').removeClass("active"); to remove all previous active class before adding new class in the below code.

function onScroll(event){
var scrollPos = $(document).scrollTop();
$('#menu-center a').each(function () {
var currLink = $(this);
var refElement = $(currLink.attr("href"));
if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) {
$('#menu-center ul li a').removeClass("active"); //added to remove active class from all a elements
currLink.addClass("active");
}
else{
currLink.removeClass("active");
}
});
}

  1. If you click on 2nd or greater menu link it makes you scroll to the location but changes active class to previous link.

Solution

$('html, body').stop().animate({
'scrollTop': $target.offset().top+2

change the active list when scrolling navbar

The first problem with your code is that you're not calling the "activeLink" function correctly.

 if(top >= offset && top < offset + height){
lists.forEach(sec =>{
activeLink;
})
}

If you add the parentheses, then it'll work and throw an error due to the "this" keyword which refers to the window object in this case.

To work, in the "onScroll" handler, you can replace your code with this piece:

if (top >= offset && top < offset + height) {
lists.forEach((item) => {
item.classList.remove('active');
if(item.querySelector('a').getAttribute('href') == '#' + id){
item.classList.add('active');
}
});
}

Of course, your aim will be not to duplicate your code. So, we can first select the target like that:

const target = document.querySelector(`[href='#${id}']`).parentElement;
activeLink(target);

Passing it to the "activeLink" function directly will lead you to check the coming parameter if it is the "click event" or the list item and handles both cases. instead, you can edit your handler code like this:

lists.forEach((item) =>
item.addEventListener('click', function(){
activeLink(this);
}));

And this is the "activeLink" function code in the final state:

function activeLink(li) {
lists.forEach((item) => item.classList.remove('active'));
li.classList.add('active');
}

Here is the code snippet for this:

let section = document.querySelectorAll('section');
let lists = document.querySelectorAll('.list');
function activeLink(li) {
lists.forEach((item) => item.classList.remove('active'));
li.classList.add('active');
}
lists.forEach((item) =>
item.addEventListener('click', function(){
activeLink(this);
}));

window.onscroll = () => {
section.forEach(sec => {
let top = window.scrollY;
let offset = sec.offsetTop;
let height = sec.offsetHeight;
let id = sec.getAttribute('id');

if (top >= offset && top < offset + height) {
const target = document.querySelector(`[href='#${id}']`).parentElement;
activeLink(target);
}
})
};
.sec {
height: 500px;
}

nav {
position: fixed;
width: 100%;
top: 0;
left: 0;
z-index: 10;
background-color: #fff;
}

.active a {
color: palevioletred;
}
<nav class="navigation">
<ul>
<li class="list active">
<a href="#home">
<span class="icon">
<ion-icon name="home-outline"></ion-icon>
</span>
<span class="title">Home</span>
</a>
</li>
<li class="list">
<a href="#about">
<span class="icon">
<ion-icon name="person-outline"></ion-icon>
</span>
<span class="title">About Me</span>
</a>
</li>
<li class="list">
<a href="#working">
<span class="icon">
<ion-icon name="newspaper-outline"></ion-icon>
</span>
<span class="title">Working</span>
</a>
</li>
<li class="list">
<a href="#Learned">
<span class="icon">
<ion-icon name="code-slash-outline"></ion-icon>
</span>
<span class="title">Learned</span>
</a>
</li>
<li class="list">
<a href="#contact">
<span class="icon">
<ion-icon name="chatbox-outline"></ion-icon>
</span>
<span class="title">Contact</span>
</a>
</li>
</ul>
</nav>

<section class="sec" style="background-color: beige;" id="home"></section>
<section class="sec" style="background-color: yellow;" id="about"></section>
<section class="sec" style="background-color: orange;" id="working"></section>
<section class="sec" style="background-color: orangered;" id="Learned"></section>
<section class="sec" style="background-color: olive;" id="contact"></section>

jquery scroll, change navigation active class as the page is scrolling, relative to sections

If you wish a more generic function:

SEE DEMO

$(window).scroll(function() {
var windscroll = $(window).scrollTop();
if (windscroll >= 100) {
$('nav').addClass('fixed');
$('.wrapper section').each(function(i) {
if ($(this).position().top <= windscroll - 100) {
$('nav a.active').removeClass('active');
$('nav a').eq(i).addClass('active');
}
});

} else {

$('nav').removeClass('fixed');
$('nav a.active').removeClass('active');
$('nav a:first').addClass('active');
}

}).scroll();​

How to keep class active when scrolling with javascript?

Something like this? I couldn't reproduce the flickering visually on my machine but I can see the class being removed/added constantly on scroll

https://jsfiddle.net/7szpuqsr/1/

Main changes, I added a class to your sections, you have too many sections but with the way your code is meant to work, it's much easier to add a class to the sections, example below

<section id="home" class="section">

var sections = $('.section') to get the section class

updated this part of the js to check for active class

if (cur_pos >= top && cur_pos <= bottom) {
if(!$(this).hasClass("active")) {
nav.find('a').removeClass('active');
sections.removeClass('active');

$(this).addClass('active');
nav.find('a[href="#'+$(this).attr('id')+'"]').addClass('active');
}
}

You can also cache the $(this) into a variable inside of the section loop like

var $this = $(this);

then just use $this for the rest of the loop

here is the doc for hasClass https://api.jquery.com/hasclass/



Related Topics



Leave a reply



Submit