3D Printing/Modeling, Woodworking, Coding and generally making stuff

Sticky Headers that scroll with or without React

I wanted to have a sticky header that scrolls out of the way when the viewer scrolls down the page but then comes back when the viewer scrolls up.

Like you see on my site:

Sticky Headers that scroll

There are two ways to do this:

I will show you how to do it with React first.

1. With React

This solution came from an article by Codemzy. I had to update it a little due to some of the code being outdated and the fact I wanted to use TailwindCSS and Next.js.

Here is the article: https://www.codemzy.com/blog/react-sticky-header-disappear-scroll/

Since I use TailwindCSS here is the updated code:

// header component
function Header() {
  const scrollDirection = useScrollDirection();

  return (
    <header className={`sticky ${scrollDirection === "down" ? "-top-16" : "top-0"} h-16 transition-all duration-500`}>
      <div>Header content goes here</div>
    </header>
  );
}

Now I wanted different sizes depending on the screen size so I came up with this:

// header component
function Header() {
  const scrollDirection = useScrollDirection();

  return (
    <header
      className={`sticky  ${scrollDirection === "down" ? "-top-12 md:-top-14 lg:-top-16" : "top-0"} h-12 transition-all duration-500 md:h-14 lg:h-16`}
    >
        {/* Your header content */}
    </header>
  );
}

The hook that is referenced in the code is this:

import { useEffect, useState } from "react";

function useScrollDirection() {
  const [scrollDirection, setScrollDirection] = useState<"down" | "up" | null>(null);

  useEffect(() => {
    let lastScrollY = window.scrollY;

    const updateScrollDirection = () => {
      const scrollY = window.scrollY;
      const direction = scrollY > lastScrollY ? "down" : "up";
      if (direction !== scrollDirection && (scrollY - lastScrollY > 10 || scrollY - lastScrollY < -10)) {
        setScrollDirection(direction);
      }
      lastScrollY = scrollY > 0 ? scrollY : 0;
    };
    window.addEventListener("scroll", updateScrollDirection); // add event listener
    return () => {
      window.removeEventListener("scroll", updateScrollDirection); // clean up
    };
  }, [scrollDirection]);

  return scrollDirection;
}

2. Next.js Without React

I am using Next.js but am aiming for a statically generated side, so no client-side React code. I am also using TailwindCSS so I will show you how to do it with Next.js, CSS, and JavaScript. This could easily be done with plain HTML, CSS, and JavaScript as well by moving the JavaScript code to a into your html head element.

Since we removed the React hook, the scroll direction state must be managed outside React's state system. We can use Tailwind's utility classes to achieve the same result.

Here is the code:

import Script from "next/script";

export function Header({ className }: { className?: string }) {
  return (
    <header
      id="main-header"
      className="sticky top-0 flex h-12 items-center justify-between px-4 py-2 transition-all duration-500 md:h-14 lg:h-16"
    >
      <Script id="header-script">
        {`
          (function() {
            var lastScrollY = window.scrollY;
            var headerElement = document.getElementById('main-header');

            function updateScrollDirection() {
              var scrollY = window.scrollY;
              var direction = scrollY > lastScrollY ? "down" : "up";
              
              // Check if the scroll distance is significant (>10)
              if (scrollY - lastScrollY > 10 || scrollY - lastScrollY < -10) {
                if (direction === "down") {
                  // Use Tailwind's translate utility to move the header
                  headerElement.classList.add('-top-12');
                  headerElement.classList.add('-top-14');
                  headerElement.classList.add('-top-16');
                  headerElement.classList.remove('top-0');
                } else {
                  // Reset translation to show the header
                  headerElement.classList.add('top-0');
                  headerElement.classList.remove('-top-12');
                  headerElement.classList.remove('-top-14');
                  headerElement.classList.remove('-top-16');
                }
              }
              lastScrollY = scrollY > 0 ? scrollY : 0;
            }

            window.addEventListener("scroll", updateScrollDirection);
          })();
        `}
      </Script>
      {/* Your header content */}
    </header>
  );
}

This code will give you the same effect as the React code but with no "use client" required.

Lastly,

3. Using pure HTML, CSS, and JavaScript (no Next.js, React or TailwindCSS)

To do this with just vanilla JavaScript and CSS we'll need to approach this slightly differently. If we are not going to use Tailwind to help us with the classes we will need to do it ourselves.

Here is how you can do it:

Step 1: Define Your Header in HTML

Ensure your header element is defined in your HTML or JSX with a specific identifier or class name that can be easily selected:

<header id="main-header">
  {/* Your header content */}
</header>

Step 2: Adapt the Vanilla JavaScript for Header Adjustment

Modify the vanilla JavaScript scroll direction detection script to directly apply the necessary classes or styles to our header based on the scroll direction:

<script>
  (function() {
    var lastScrollY = window.scrollY;
    var headerElement = document.getElementById('main-header');

    function updateScrollDirection() {
      var scrollY = window.scrollY;
      var direction = scrollY > lastScrollY ? "down" : "up";
      
      // Check if the scroll distance is significant (>10)
      if (Math.abs(scrollY - lastScrollY) > 10) {
        if (direction === "down") {
          // Adjust these classes based on your specific needs
          headerElement.classList.add('hide-header');
          headerElement.classList.remove('show-header');
        } else {
          headerElement.classList.add('show-header');
          headerElement.classList.remove('hide-header');
        }
      }
      lastScrollY = scrollY > 0 ? scrollY : 0;
    }

    window.addEventListener("scroll", updateScrollDirection);
  })();
</script>

Now given this is a non-Tailwind approach we will need to add some basic CSS. In our main css file we need to define the .hide-header and .show-header classes (or whatever you choose to name them) to reflect the position adjustments you want based on the scroll direction. So in this example:

#main-header {
  position: sticky;
  top: 0;
  display: flex;
  height: 3rem; /* Equivalent to h-12 */
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 1rem; /* Equivalent to px-4 py-2 */
  transition: all 0.5s; /* Equivalent to transition-all duration-500 */
}

/* Responsive Design */
@media (min-width: 768px) { /* Equivalent to md: */
  #main-header {
    height: 3.5rem; /* Equivalent to md:h-14 */
  }
}

@media (min-width: 1024px) { /* Equivalent to lg: */
  #main-header {
    height: 4rem; /* Equivalent to lg:h-16 */
  }
}

.hide-header {
  /* Move header out of view */
  transform: translateY(-100%);
}
.show-header {
  /* Ensure header is in its normal position */
  transform: translateY(0);
}

This setup should work well for straightforward cases. However, if your application's interaction with the scroll behavior grows more complex, you may need to consider a more robust solution.

I hope this helps you to create a sticky header that scrolls out of the way when the viewer scrolls down the page but then comes back when the viewer scrolls up.

If you have any questions or comments please let me know.

Happy coding!

Resources

March 11, 2024 (3mo ago)

code

#web#react#typescript