JavaScript 100 views

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.

By TWC Team • Feb 11, 2026

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();
})();
Back to Snippets