Auto Shrink Header On Scroll in React

ยท

4 min read

I've always liked this effect: keep the header of your website sticky then auto-shrink and blur when users scroll down.

Demo

Today, I finally got some free time to implement it for my website, so I'm writing this article hopefully to help you do the same for yours if you also like it ๐Ÿ˜‰.

Motivations

There're 2 primary motivations that makes me love this effect:

  • You can put a couple of calls to action (CTAs), then as your users are reading content on your website, they keep seeing these CTAs, which can increase the odds that they'll click them. I like to keep links to my newsletter and Twitter here. (An extra tip that I like to do here is to make a little animation or transition that occasionally runs to remind the user's the CTAs)

  • I want to optimize for CTAs but I don't want users on my website to have bad experience, which is actually even more important. To avoid bad UX, the header shouldn't take too much space, especially when users are reading the main content.

How-to

Basic idea

The basic idea is to subscribe to onscroll event of the browser, then check if the user scrolls pass a certain offset and update CSS of the header component arcordingly.

Subscribe to onscroll using hook

const Header = () => {
  useEffect(() => {
    const handler = () => {
      // Check and update component here.
    };

    window.addEventListener("scroll", handler);
    return () => window.removeEventListener("scroll", handler);
  }, []);

  // return <Component ... />
};

Use useEffect hook to subscribe to event onscroll when the component is mounted (notice the last parameter []), also remember to return an unsubscribe function when the component is unmounted to avoid memory leaks.

Check for scroll position

const Header = () => {
  const [isShrunk, setShrunk] = useState(false);

  useEffect(() => {
    const handler = () => {
      setShrunk((isShrunk) => {
        if (
          !isShrunk &&
          (document.body.scrollTop > 20 ||
            document.documentElement.scrollTop > 20)
        ) {
          return true;
        }

        if (
          isShrunk &&
          document.body.scrollTop < 4 &&
          document.documentElement.scrollTop < 4
        ) {
          return false;
        }

        return isShrunk;
      });
    };

    // Previous logic.
  }, []);

  // return <Component isShrunk={isShrunk} />
};

Notice setShrunk is called with a function instead of just pure value, this pattern helps ensure we are checking against the lastest previous value. Also, there are a gap between offsets to shrink and to expand (20 and 4), this helps avoid flashing of changed styles.

That's it. I hope it helps. Check out the full source code to see more details.