Dynamic Sticky Navigation Bar with Scroll Effects
This snippet creates a dynamic sticky navigation bar that changes style on scroll, enhancing user experience on long pages.
Code
/**
* Dynamic Sticky Navbar with Scroll Effects
* Adds/removes a `.sticky` class based on scroll position, with performant scroll handling.
* Usage: Ensure your navbar has `.navbar` and define `.navbar.sticky` styles in CSS.
*/
(() => {
'use strict';
const initStickyNavbar = async ({ selector = '.navbar', stickyClass = 'sticky', offset = 0 } = {}) => {
try {
const navbar = document.querySelector(selector);
if (!navbar) throw new Error(`StickyNavbar: No element found for selector "${selector}"`);
// Recompute the scroll threshold when layout changes (fonts/images/resizes).
let stickyTop = 0;
const computeStickyTop = () => {
const { top } = navbar.getBoundingClientRect();
stickyTop = top + window.pageYOffset + Number(offset || 0);
};
// Wait for fonts when available to reduce layout shift affecting threshold.
if (document.fonts?.ready) await document.fonts.ready;
computeStickyTop();
// Performance: use passive listener + rAF to avoid jank on frequent scroll events.
let ticking = false;
const update = () => {
ticking = false;
const shouldStick = window.pageYOffset > stickyTop;
navbar.classList.toggle(stickyClass, shouldStick);
};
const onScroll = () => {
if (ticking) return;
ticking = true;
window.requestAnimationFrame(update);
};
window.addEventListener('scroll', onScroll, { passive: true });
// Keep threshold correct on resize/orientation changes and when images load.
window.addEventListener('resize', computeStickyTop, { passive: true });
window.addEventListener('orientationchange', computeStickyTop, { passive: true });
window.addEventListener('load', () => {
computeStickyTop();
update(); // Ensure correct state after late-loading assets.
}, { once: true });
// Initialize state in case the page loads mid-scroll.
update();
// Optional: return a cleanup function to make this reusable in SPA contexts.
return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', computeStickyTop);
window.removeEventListener('orientationchange', computeStickyTop);
};
} catch (error) {
console.error(error);
return () => {};
}
};
// Example usage:
// initStickyNavbar({ selector: '.navbar', stickyClass: 'sticky', offset: 0 });
void initStickyNavbar();
})();