I have a nav menu which I want to update whenever a user scrolls past the section it's referencing.
For example, if the user scrolls past #section-1
, I want .configurator__menu-link
with the href="#section-1"
to become orange.
However, my current logic doesn't work, and I'm unsure why. Initially I thought it may be that my scroll position is being calculated incorrectly because of my scrollTrigger
. But, having commented out the code that pins the section, the results are still the same.
Demo here:
$(function() {
gsap.registerPlugin(ScrollTrigger);
let container = document.querySelector(".configurator");
let pin = document.getElementById("configurator-pin");
let freeroam = document.getElementById("configurator-freeroam");
ScrollTrigger.create({
trigger: container,
start: "top",
endTrigger: freeroam,
end: () => `bottom 0%+=${pin.offsetHeight}`,
invalidateOnRefresh: true,
pin: pin,
markers: true
});
ScrollTrigger.create({
trigger: container,
start: "top",
endTrigger: freeroam,
end: () => `bottom 0%+=${pin.offsetHeight}`,
invalidateOnRefresh: true,
pin: ".configurator__header",
markers: true,
pinSpacing: false
});
// UPDATE CLASS ON SCROLL
var menu = $(".configurator__menu"),
menu_link = $(".configurator__menu-link"),
active_class = "configurator__menu-link--active";
$(window).scroll(function() {
var scrollTop = $(document).scrollTop();
var anchors = menu.find(menu_link);
for (var i = 0; i < anchors.length; i++) {
if (scrollTop > $(anchors[i]).offset().top - 50 && scrollTop < $(anchors[i]).offset().top + $(anchors[i]).height() - 50) {
$('.configurator__menu-link[href="#' + $(anchors[i]).attr('id') + '"]').addClass(active_class);
} else {
$('.configurator__menu-link[href="#' + $(anchors[i]).attr('id') + '"]').removeClass(active_class);
}
}
});
});
.spacer {
height: 200px;
background: lightblue;
}
.configurator {
overflow: hidden;
}
.configurator__header {
padding: 30px 0;
background-color: #FFFFFF;
z-index: 99;
width: 100%!important;
max-width: 100%!important;
}
.configurator__options {
background: lightblue;
padding: 30px;
}
.configurator__options-inner {
padding: 60px 30px;
}
.configurator__options-inner-section{
margin-bottom: 60px;
}
.configurator__images img {
width: 100%;
}
.footer {
height: 300px;
background-color: lightblue;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/ScrollTrigger.min.js"></script>
<div class="spacer"></div>
<section class="configurator">
<div class="container">
<div class="row">
<div class="col-12">
<header class="configurator__header">
<nav class="configurator__menu justify-content-between">
<a class="configurator__menu-link" href="#section-1">Section 1</a>
<a class="configurator__menu-link" href="#section-2">Section 2</a>
</nav>
</header>
</div>
</div>
</div>
<div class="container-fluid px-0">
<div class="row">
<div class="col-6">
<div class="configurator__images" id="configurator-pin">
<img src="https://via.placeholder.com/840x514?text=Image" alt="test" />
</div>
</div>
<div class="col-6">
<div class="configurator__options">
<div class="configurator__options-inner" id="configurator-freeroam">
<div class="configurator__options-inner-section" id="section-1">
<h2>Section 1</h2>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
</div>
<div class="configurator__options-inner-section" id="section-2">
<h2>Section 2</h2>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="footer"></footer>
In reply to inwerpsel's answer:
Many thanks for showing me this approach, and in hindsight, agree that it's much better than firing code after every scroll event.
I've tested your approach, and whilst it does work, wondering if there's room to optimise it further?
The first thing I'm noticing is that whenever the next section is in view, even slightly, then that nav link will become active. For example, if I'm scrolling down #section-1
and #section-2
is in view, even a little, then that link for #section-2
is active. Whereas from a UX perspective, the majority of the content on their screen is still from #section-1
.
Is there a way to delay the active state unless it's 50% in view?
In the GIF you can see that #section-3
isn't even in view, yet its link was active.
Same issue when scrolling back up. Even though most of a section is in view, it will make the previous link active, which is confusing UX wise.
Any way to optimise this further?
Code for updated demo:
$(function() {
var menu = $(".configurator__menu"),
menu_link = $(".configurator__menu-link"),
active_class = "configurator__menu-link--active";
const activateMenuItem = id => {
$('.configurator__menu-link').removeClass(active_class);
$(`.configurator__menu-link[href="#${id}"]`).addClass(active_class);
}
const options = {
// No root option provided means check against the window.
threshold: 0,
}
activateVisibleSectionMenu = (entries) => {
entries.forEach(entry => {
console.log(entry);
if (entry.isIntersecting) {
// Should be as it becomes the first element on the page.
activateMenuItem(entry.target.id);
}
});
}
const observer = new IntersectionObserver(activateVisibleSectionMenu, options);
const sections = document.querySelectorAll('.configurator__options-inner-section');
sections.forEach(section => observer.observe(section));
let container = document.querySelector(".configurator");
let pin = document.getElementById("configurator-pin");
let freeroam = document.getElementById("configurator-freeroam");
ScrollTrigger.create({
trigger: container,
start: "top",
endTrigger: freeroam,
end: () => `bottom 0%+=${pin.offsetHeight}`,
invalidateOnRefresh: true,
pin: pin,
// markers: true
});
ScrollTrigger.create({
trigger: container,
start: "top",
endTrigger: freeroam,
end: () => `bottom 0%+=${pin.offsetHeight}`,
invalidateOnRefresh: true,
pin: ".configurator__header",
// markers: true,
pinSpacing: false
});
});
.spacer {
height: 200px;
background: lightblue;
}
.configurator {
overflow: hidden;
}
.configurator__header {
padding: 30px 0;
background-color: #FFFFFF;
z-index: 99;
width: 100%!important;
max-width: 100%!important;
}
.configurator__options {
background: lightblue;
padding: 30px;
}
.configurator__options-inner {
padding: 60px 30px;
}
.configurator__options-inner-section {
margin-bottom: 60px;
}
.configurator__images img {
width: 100%;
}
.footer {
height: 300px;
background-color: lightblue;
}
.configurator__menu-link--active {
background: yellow !important;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/ScrollTrigger.min.js"></script>
<div class="spacer"></div>
<section class="configurator">
<div class="container">
<div class="row">
<div class="col-12">
<header class="configurator__header">
<nav class="configurator__menu justify-content-between">
<a class="configurator__menu-link" href="#section-1">Section 1</a>
<a class="configurator__menu-link" href="#section-2">Section 2</a>
<a class="configurator__menu-link" href="#section-3">Section 3</a>
</nav>
</header>
</div>
</div>
</div>
<div class="container-fluid px-0">
<div class="row">
<div class="col-6">
<div class="configurator__images" id="configurator-pin">
<img src="https://via.placeholder.com/840x514?text=Image" alt="test" />
</div>
</div>
<div class="col-6">
<div class="configurator__options">
<div class="configurator__options-inner" id="configurator-freeroam">
<div class="configurator__options-inner-section" id="section-1">
<h2>Section 1</h2>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
</div>
<div class="configurator__options-inner-section" id="section-2">
<h2>Section 2</h2>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
</div>
<div class="configurator__options-inner-section" id="section-3">
<h2>Section 3</h2>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="footer"></footer>