Show and hide a header based on scroll direction

Show and hide a header based on scroll direction

Chris Bongers
Β·Jan 9, 2022Β·

5 min read

Play this article

This article actually has a funny origin as it was requested by my good friend Fredrik asked me to help with a specific menu.

He initially reached out to me, thanking me for writing down the article on showing a menu on scroll.

And he wanted to create something similar to the Pentagram website.

Let's take a moment to see what happens and what kind of actions we need to focus on.

  1. We see the header with no background sitting over an image
  2. On scroll, the header disappears like a regular element
  3. Once we scroll down and pass the first viewport height, the following actions can happen
  4. Scroll up, the menu re-appears with a background
  5. Scroll down, the menu disappears again
  6. When we hit the viewport height, it always disappears again

I've done some more research on this website, and they actually use two headers to achieve this effect. However, I'm going to show you how to do this with just one!

The result for today can be seen in this CodePen.

Sketching a solution

Let's start by wireframing an HTML setup to work with. I went for a straightforward approach and came up with the following.

  <section><img src="img.jpg" alt="colorfull passage" /></section>
  <section><img src="img.jpg" alt="colored leafs" /></section>

As you can see, we have the header as one element and a main wrapper with some sections.

I'll start by making each section the exact size of the viewport. This will make the effect stand out a bit more.

section {
  background: #efefef;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;

Note: You can find the complete CSS in the CodePen example.

Then we have to start working on the initial header styling. As mentioned, it should be an absolute positioned element, so it will scroll away initially.

header {
  position: absolute;
  width: 100%;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.4);

Handling scroll events in JavaScript

Now I think it's time to start adding some scroll listeners in JavaScript.

As you might know, listening to scroll events has a high impact on performance because it fires too often. Especially on mobile devices, it fires like crazy.

So, we want to add some kind of threshold to not fire too many events.

I've decided on a 100ms delay of firing. You can play around with this value. However, it will impact when it adds/removes certain classes to get weird behaviors.

The throttle function looks like this:

const throttle = (func, time = 100) => {
  let lastTime = 0;
  return () => {
    const now = new Date();
    if (now - lastTime >= time) {
      time = now;

Basically, this will check if enough time is passed. If that is the case, we fire the func() we passed as an argument.

To use this we can wrap the function we want to use for the scroll effect like so:

window.addEventListener('scroll', throttle(validateHeader, 100));

So on scroll, but only after 100ms will we fire a validateHeader function.

Before building this function, let's set up some variables we need. We want to have the header element and the last scrolled position in this case.

const header = document.querySelector('header');
let lastScroll = 0;

Now it's time to make the validateHeader function.

const validateHeader = () => {
  // todo

We can start by getting the current scroll offset and the screen size.

const windowY = window.scrollY;
const windowH = window.innerHeight;

The first check we need to do is determine if we scrolled past the first viewport height (windowH).

if (windowY > windowH) {
  // We passed the first section, set a toggable class
} else {
  header.classList.remove('is-fixed', 'can-animate');

We will add a new class to our header if this is the case. This class is the is-fixed class.

If the scroll is not high enough, we remove this class and the can-animate class we'll add in a second.

This is-fixed class looks like this:

header {
  &.is-fixed {
    background: rgba(255, 255, 255, 0.9);
    position: fixed;
    transform: translate3d(0, -100%, 0);

This class changes the header from absolute to fixed and makes sure it's hidden initially. It also changes the background of the header.

The next thing we need is to determine if we passed the viewport height + the size of the header. I split these two to prevent flickering from happening because of the animation we will set.

// Determine is we ready to animate
if (windowY > windowH + 40) {
} else {

This can-animate class will add the smooth animation we want. However, as mentioned, we don't want to on the first load. That's why we split the two.

header {
  &.can-animate {
    transition: transform 0.3s ease, visibility 0s 0.3s linear;

The last part of this puzzle is the actual show once we scroll upwards.

if (windowY < lastScroll) {
} else {

You can see we evaluate if the window position is smaller than the last scrolled position. If yes, it means we should scroll up and add the scroll-up class.

This class will transform the negative position of the header.

header {
  &.scroll-up {
    transform: translate3d(0, 0, 0);

The last thing this function needs is to update the last scroll position with the current one.

lastScroll = windowY;

And that's it, we got ourselves a header that can change appearance once it passes the first viewport height. And it will show only on scroll up.

Note: You can see the complete code in the embedded CodePen.

I hope you enjoyed this article. I would love to see what you used this for.

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Did you find this article valuable?

Support Chris Bongers by becoming a sponsor. Any amount is appreciated!

See recent sponsors |Β Learn more about Hashnode Sponsors
Share this