The Situation
I have a fixed nav bar at the top of the page. As you scroll down through different sections of the page the nav bar dynamically updates (underlines and highlights). You can also click a section on the nav bar and it will scroll down to that section.
This is done using the intersection observer API to detect which section it's on and scrollIntoView to scroll to each section.
The Problem
Lets say you are on section 1 and you click the last section, 5, and it scrolls the page down past all the other sections in-between. The scroll is fast and as it scrolls all the sections are detected by the intersection observer and therefore the nav is updated. You end up getting an effect of the nav quickly changing for each nav item as it goes past each corresponding section.
The Goal
How do you delay the intersection observer from triggering the menu change if the section is only in frame for a millisecond? When quickly scrolling the nav bar should only update once the scrolling has stopped on a section.
Code Setup
const sectionItemOptions = {
threshold: 0.7,
};
const sectionItemObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// select navigation link corresponding to section
} else {
// deselect navigation link corresponding to section
}
});
}, sectionItemOptions);
// start observing all sections on page
sections.forEach((section) => {
sectionItemObserver.observe(section);
});
Ideas
My first thought was to put a setTimeout so that the nav wouldn't change until the Timeout was finished, then cancel the Timeout if the section left the screen before the timeout finished. But as the timeout is in a forEach loop this didn't work.
const sectionItemObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
let selectNavTimeout
if (entry.isIntersecting) {
// Set timeout when section is scrolled past
selectNavTimeout = setTimeout(() => {
// select navigation link corresponding to section
}, 1000)
} else {
// deselect navigation link corresponding to section
// cancel timeout when section has left screen
clearTimeout(selectNavTimeout)
}
});
}, sectionItemOptions);
Any other ideas would be greatly appreciated! Thanks :)
After lots of brainstorming I came up with an idea that didn't exactly answer the question of delaying the Intersection Observer API but it did solve the problem of the nav bar flickering.
The highlighting of the nav item is done through adding an "is-active" class onto it and then applying CSS to it. Because the "is-active" class is only on the nav item for a split second you can use CSS keyframes to delay the application of CSS styles. By the time the delay has finished the "is-active" class isn't present on the nav item and no styles are changed.
Keeping the original JS the same this is the CSS used
.is-active {
animation: navItemSelected;
animation-duration: 0.3s;
// delay longer than the time nav item is in frame
animation-delay: 0.1s;
// fill mode to hold animation at the end
animation-fill-mode: forwards;
}
@keyframes navItemSelected {
// default non-selected style of nav item
from {
font-style: normal;
opacity: 0.5;
}
// highlighted style of nav item
to {
font-style: italic;
opacity: 1;
}
}