Scroll Down to See the Natural Hide Effect

You know what? Lorem ipsum is boring. Instead of meaningless placeholder text, let's show you the actual TypeScript source code that makes this header behavior work!

💡 Pro tip: Try clicking and dragging to scroll! This gives you precise control over scroll speed and direction, just like on mobile devices.

Fun fact: This header uses the default settings: snapEagerness: 1.0 and scrollThreshold: 0. This entire script is only 1.0KB minified and has zero dependencies. Pure TypeScript compiled to vanilla JavaScript.

🔍 The Magic Behind This Header Animation

/**
 * Attaches a natural hide/show behavior to a sticky element placed at the top.
 * 
 * @param element - The HTML element to make naturally sticky
 * @param options - Configuration options
 * @param options.snapEagerness - How eagerly element snaps into sticky position (default: 1)
 * @param options.scrollThreshold - Minimum scroll speed to trigger natural scroll-in effect (default: 0)
 */
export function naturalStickyTop(element: HTMLElement, options?: { 
  snapEagerness?: number; 
  scrollThreshold?: number 
}) {
  let lastScrollY = window.scrollY;
  let isSticky = false;
  let isHeaderAtTop = true;
  const snapEagerness = options?.snapEagerness ?? 1;
  const scrollThreshold = options?.scrollThreshold ?? 0;

  const handleScroll = () => {
    const currentScrollY = window.scrollY;
    const elementRect = element.getBoundingClientRect();
    const scrollStep = currentScrollY - lastScrollY;
    const isElementVisible = elementRect.bottom > 0 && elementRect.top < window.innerHeight;

    // Handle all relative mode logic first
    if (!isSticky) {
      // First priority: Check if element should switch to sticky
      // Predict where element will be on next scroll event to avoid visual gaps
      if (elementRect.top - snapEagerness * scrollStep >= 0) {
        isSticky = true;
        isHeaderAtTop = false;
        element.style.position = 'sticky';
        element.style.top = '0';
      }
      // Second priority: Check if scrolling up with enough speed to trigger scroll-in effect
      else if (-scrollStep >= scrollThreshold && !isElementVisible) {
        isHeaderAtTop = false;
        element.style.position = 'relative';
        // Position element just above viewport so it scrolls into view naturally
        element.style.top = `${currentScrollY - element.offsetHeight}px`;
      }
      // Third priority: When header becomes invisible, move it to top of document
      else if (!isHeaderAtTop && !isElementVisible) {
        isHeaderAtTop = true;
        element.style.position = 'relative';
        element.style.top = '0px';
      }
    }
    // Handle sticky mode logic - release from sticky when scrolling down
    else if (scrollStep > 0) {
      isSticky = false;
      element.style.position = 'relative';
      // Position element at current scroll position so it moves naturally with content
      element.style.top = `${currentScrollY}px`;
    }

    lastScrollY = currentScrollY > 0 ? currentScrollY : 0;
  };

  handleScroll();
  window.addEventListener('scroll', handleScroll, { passive: true });

  return { destroy: () => window.removeEventListener('scroll', handleScroll) };
}

Pretty neat, right? The key insight is that instead of using CSS transitions or JavaScript animations, we let the browser's natural scrolling do all the work. When you scroll down, the header just goes along for the ride. When you scroll up, we position it just above the viewport so it naturally scrolls back into view.

Keep scrolling to see more of how this works...

🎯 How It Works - The Natural Approach

Traditional sticky headers often use slide animations that can feel jarring and distracting:

Our natural approach is different - it's designed to be less distracting:

⚙️ The Three States Explained

The header operates in three distinct modes:

1. Sticky Mode: position: sticky; top: 0
Header is visible and sticks to the top of the viewport
2. Released Mode (scrolling down): position: relative; top: currentScrollY
Header flows naturally with the content as you scroll down
3. Preparation Mode (scrolling up): position: relative; top: currentScrollY - elementHeight
Header is positioned just above the viewport, ready to scroll into view

The secret sauce is in the positioning logic. When you scroll down, we switch from position: sticky to position: relative and set the top position to the current scroll position. This makes the element flow naturally with the content as you scroll down.

When you scroll back up and the element isn't visible yet, we position it just above the viewport. As you continue scrolling up, it naturally comes into view, and once it reaches the top, we switch it back to position: sticky.

🌐 Browser Compatibility

Works in all modern browsers that support position: sticky (which is pretty much everything since 2017). No polyfills needed, no feature detection required - just works.

📦 Bundle Size Comparison

Let's put that 1.0KB into perspective:

That's 87% smaller than the most popular alternatives, with better performance and more natural animations!

Try scrolling up and down a few more times to really feel the difference. Notice how the header movement feels completely natural, like it's just part of the page content flowing with your scroll. No artificial timing, no disconnected animations - just pure, natural movement.