James Ives James Ives - 2 months ago 6
Javascript Question

Updating Class on Scroll Issue

I'm working on a page with a fixed menu on the right side which is generated whenever a header with the

link-list-item
class is present. I want to update the menu with an active class whenever these items are passed on the scroll. The issue I'm running into is that it's only updated whenever the scroll reaches the title, after which it is removed. I want the menu item to keep the active class until it reaches the next title, or if there's no header above the scroll point. Wrapping each section in a div is not an option with the way the page is made up.

Here's my markup:

<div class="col-xs-6">
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<header class="header-tab-label row">
<h5 id="alaska" class="link-list-label">Alaska</h5>
</header>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<header class="header-tab-label row">
<h5 id="austin" class="link-list-label">Austin</h5>
</header>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<header class="header-tab-label row">
<h5 id="england" class="link-list-label">England</h5>
</header>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
<p>Your bones don't break, mine do. That's clear.</p>
</div>


Javascript:

(function($) {

$linklistLabel = $(".link-list-label");
$linkList = $(".link-list");
$linkListItem = $linkList.find("li");

$linklistLabel.each(function(index) {
$linkList.append('<a href="#' + $(this).text().toLowerCase() + '"><li>' + $(this).text() + '</li></a>');
});

function onScroll(event) {
var scrollPos = $(document).scrollTop();
$linkList.find('a').each(function() {
var currLink = $(this);
console.log(currLink);
var refElement = $(currLink.attr("href"));
if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) {
$linkListItem.removeClass("active");
currLink.addClass("active");
} else {
currLink.removeClass("active");
}
});
}

$(document).ready(function() {
$(document).on("scroll", onScroll);

//smoothscroll
$('a[href^="#"]').on('click', function(e) {
e.preventDefault();
$(document).off("scroll");

$('a').each(function() {
$(this).removeClass('active');
})
$(this).addClass('active');

var target = this.hash,
menu = target;
$target = $(target);
$('html, body').stop().animate({
'scrollTop': $target.offset().top + 2
}, 500, 'swing', function() {
window.location.hash = target;
$(document).on("scroll", onScroll);
});
});
});

})(jQuery);


JSFiddle: https://jsfiddle.net/kx8Ljm9t/1/

Answer

Very quickly coded - this would need more testing:

$linkListA = $(".link-list a");
function onScroll(event) {
      var scrollPos = $(document).scrollTop();
      for (var i = 0; i < $linklistLabel.length; i++) {
         if ($linklistLabel.eq(i).offset().top - scrollPos < 100 &&
          $linklistLabel.eq(i).offset().top - scrollPos > -100) {
                 $linkListA.removeClass('active');
                 var id = $linklistLabel.eq(i).prop('id');
                 $('a[href="#'+id+'"]').addClass('active');
         } else if ($linklistLabel.eq(0).offset().top - scrollPos  >= 100) {
                 $linkListA.removeClass('active');
         }
      }
};

I prefer it the other way around, meaning at every scroll I loop over all headers (and not over the links). So at every scroll movement you look if there is a header within 100px to the top and to the bottom - if yes remove the active class from all links and add the class for the current header.

Fiddle: https://jsfiddle.net/n0scg0m5/

Comments