This script issues the active class for the active section. Recently noticed that it stops working on small screens. Even in the developer’s console in chrome, I will start to increase the screen size and it will appear, as soon as I start to reduce it immediately stops working (the active class disappears). But only for one long section, in the shorter ones everything works. How can this be fixed?
In the snippet, I set a large fixed height, so the portfolio link does not receive the active class, in my example, when the section width increases, its height decreases, so at some point everything starts working.
const links = document.querySelectorAll('.nav-link'); const sections = [... document.querySelectorAll('.forJS')]; const callback = (entries) => { links.forEach((link) => link.classList.remove('active')); const elem = entries.find((entry) => entry.isIntersecting); if (elem) { const index = sections.findIndex((section) => section === elem.target); links[index].classList.add('active'); } } let observer = new IntersectionObserver(callback, { rootMargin: '0px', threshold: 0.5 }); sections.forEach((section) => observer.observe(section));
section { height: 100vh; scroll-y: auto; } .long{ height: 300vh; } .nav-link.active{ color: red; }
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.min.js"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/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 long">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>
Advertisement
Answer
The main issue is threshold: 0.5
. This tells the observer to fire once 50% of the element is visible in the viewport. For your “long” element, since its 300vh tall, and your viewport is 100vh tall, the maximum visibility that it has is 100vh/300vh = 33%, so the observer never fires.
To deal with this, you could adjust the threshold to something smaller like 0.25. That would fix the behavior for the long section, but it would make the active link change early for your shorter sections. So I propose you add 2 observers: 1 for the short sections with a threshold of 0.5 (.forJS:not(.long)
), and another for the longer sections with a threshold of 0.25 (.forJS.long
).
const links = document.querySelectorAll('.nav-link'); const sectionsShort = [...document.querySelectorAll('.forJS:not(.long)')]; const sectionsLong = [...document.querySelectorAll('.forJS.long')]; const sections = [...document.querySelectorAll('.forJS')]; const callback = entries => { links.forEach((link) => link.classList.remove('active')); const elem = entries.find((entry) => entry.isIntersecting); if (elem) { const index = sections.findIndex((section) => section === elem.target); links[index].classList.add('active'); } } const observerShort = new IntersectionObserver(callback, { rootMargin: '0px', threshold: .5, }); const observerLong = new IntersectionObserver(callback, { rootMargin: '0px', threshold: .25, }); sectionsShort.forEach((section) => { observerShort.observe(section) }); sectionsLong.forEach((section) => { observerLong.observe(section) });