14

I used react.js Hooks with useState and useEffect, when I scroll-down and the screen comes down Header hides after 250 pixels. Now I want to know how to display Header using the react Hooks when I scroll up.

const Navbar = () => {
  const [show, setShow] = useState(false)
  const controlNavbar = () => {
      if (window.scrollY > 250 ) {
          setShow(true)
      }else{
        setShow(false)
      }
  }

  useEffect(() => {
      window.addEventListener('scroll', controlNavbar)
      return () => {
          window.removeEventListener('scroll', controlNavbar)
      }
  }, [])

and header:

 <header className={`active ${show && 'hidden'}`}></header>

css:

.active{
    height: 4rem;
    width: 100%;
    position: fixed;
    top: 0px;
    transition: 0.3s linear;
    display: flex;
    justify-content:stretch;
    align-items: center;
    background-color: #FFFFFF;
    border-bottom: 1px solid rgba(0, 0, 0, .1);
    z-index: 40;
    box-shadow: 0 2px 5px -1px rgba(0, 0, 0, .08);
    /* padding: 0 7%; */
}
.hidden{
    height: 4rem;
    width: 100%;
    z-index: 40;
    border-bottom: 1px solid rgba(0, 0, 0, .1);
    box-shadow: 0 2px 5px -1px rgba(0, 0, 0, .08);
    position: fixed;
    top: -80px;
    transition: 0.3s linear;

}
  • Does this answer your question? [How to reveal a React component on scroll](https://stackoverflow.com/questions/38114715/how-to-reveal-a-react-component-on-scroll) – Steffano Festa Oct 06 '21 at 23:49

5 Answers5

18

Instead of using a static value (250), you need to perform dynamic checking with the previous scroll. This is my complete solution (using nextJS):

import React, { useState, useEffect } from 'react';

const Navbar = () => {
  const [show, setShow] = useState(true);
  const [lastScrollY, setLastScrollY] = useState(0);

  const controlNavbar = () => {
    if (typeof window !== 'undefined') { 
      if (window.scrollY > lastScrollY) { // if scroll down hide the navbar
        setShow(false); 
      } else { // if scroll up show the navbar
        setShow(true);  
      }

      // remember current page location to use in the next move
      setLastScrollY(window.scrollY); 
    }
  };

  useEffect(() => {
    if (typeof window !== 'undefined') {
      window.addEventListener('scroll', controlNavbar);

      // cleanup function
      return () => {
        window.removeEventListener('scroll', controlNavbar);
      };
    }
  }, [lastScrollY]);

  return (
        <nav className={`active ${show && 'hidden'}`}>
        ....
        </nav>
  );
};

export default Navbar;
Hadi Masoumi
  • 1,153
  • 9
  • 13
  • No need to check if `window` is `undefined` or not. `useEffect` only runs when the client has loaded so `window` would be defined. – Nima Aug 14 '23 at 11:32
6

Simplest answer using tailwindCSS

import React, {useState, useEffect} from 'react'

const Navbar = () => {

const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true)

const handleScroll = () => {
    const currentScrollPos = window.scrollY

    if(currentScrollPos > prevScrollPos){
        setVisible(false)
    }else{
        setVisible(true)
    }

    setPrevScrollPos(currentScrollPos)
}

useEffect( () => {
    window.addEventListener('scroll', handleScroll);

    return () => window.removeEventListener('scroll', handleScroll)
})

return (
    <div className={`bg-slate-700 h-14 sticky ${visible ? 'top-0' : ''} `}>
        Some Company Name
    </div>
)
}

export default Navbar
jaypavendano
  • 110
  • 1
  • 6
1

I have come up with better approach which is much optimized.

useEffect(() => {
    let previousScrollPosition = 0;
    let currentScrollPosition = 0;

    window.addEventListener('scroll', function (e) {

      // Get the new Value
      currentScrollPosition = window.pageYOffset;

      //Subtract the two and conclude
      if (previousScrollPosition - currentScrollPosition < 0) {
        setShow(false);
      } else if (previousScrollPosition - currentScrollPosition > 0) {
        setShow(true);
      }

      // Update the previous value
      previousScrollPosition = currentScrollPosition;
    });
  }, []);
  • Using `oldValue` and `newValue` is a bit ambiguous, would be better to use names that explain what is going on inside the variables – ViktorMS Jan 11 '23 at 09:08
  • Agreed. A better naming convention for oldValue and newValue would be previousScrollPosition and currentScrollPosition. – Suzit Kumar Jan 30 '23 at 10:47
0

From a couple of searches here I stumbled upon a scroll funtion on raw js but you can implement it in your case and tweak it how you want it, because currently your function will only use the false if scrollY < 250...

On your controlNav function create a variable that will track the location/point of your previous scroll then compare it to the current value of the scroll.. it will look like this:

     const controlNavbar = () => {
      if (window.scrollY >= this.lastScroll ) {
          setShow(true)
      }else{
        setShow(false)
      }
    this.lastScroll = window.scrollY;


  }

NB This will only work for scroll up and scroll down with no minimum value..

reference to the raw js

-1
export const useScrollHeight = () => {
  const [isFixed, setIsFixed] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      const scrollY = window.scrollY || 0;
      setIsFixed(scrollY >= window.innerHeight);
    };

    document.addEventListener("scroll", handleScroll);
    return () => {
      document.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return isFixed;
};

The window.innerHeight is 100vh of the viewport height. You can change this as per your need, like a window.innerHeight/2 for half scroll, etc.

Then you can use this as below

const isFixed = useScrollHeight();

I am using framer motion for transition, you can use whatever approach suits you best.

<>
  <motion.div
    style={{
      position: "fixed",
      top: 0,
      left: 0,
      width: "100%",
      zIndex: 10
    }}
    initial={{
      opacity: 0,
      y:"-100%"
    }}
    animate={{
      opacity: 1,
      y: isFixed ? "0%" : "-100%"
    }}
    transition={headerTransition}
  >
    <HeaderContent />
  </motion.div>
  {!isFixed && (
    <HeaderContent />
  )}
</>
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129