how to Show or hide Navbar when scroll use react.js?
Asked Answered
A

7

15

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;

}
Alacrity answered 6/10, 2021 at 22:12 Comment(1)
Does this answer your question? How to reveal a React component on scrollEnsure
S
24

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 (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(() => {
    window.addEventListener('scroll', controlNavbar);

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

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

export default Navbar;
Stalker answered 4/3, 2022 at 18:53 Comment(2)
No need to check if window is undefined or not. useEffect only runs when the client has loaded so window would be defined.Badge
Nit: use useRef instead of useState for scroll position. Because scroll position is not needed for rendering.Medovich
C
7

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
Chevaldefrise answered 31/8, 2022 at 3:18 Comment(0)
L
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;
    });
  }, []);
Leitmotif answered 30/8, 2022 at 3:55 Comment(2)
Using oldValue and newValue is a bit ambiguous, would be better to use names that explain what is going on inside the variablesKnob
Agreed. A better naming convention for oldValue and newValue would be previousScrollPosition and currentScrollPosition.Leitmotif
P
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

Penang answered 6/11, 2021 at 19:34 Comment(0)
M
0

So to hide a Menu/header bar on scrolling down and then show it on scrolling up, use normalized-wheel npm package. It gives you the position of the current Y axis. So positive for scrolling down and negative for scrolling up. check this CODE DEMO out. This demo will show you exactly what I said. For desktop I am using onscroll and onwheel. But for mobile version the onwheel and onscroll event does not work properly you have to use touch events like ontouchmove, ontouchstart and ontouchend. Hope this helps.

Edit: Make sure you add the react events on your top most element that contains all your content.

Matta answered 14/4 at 8:19 Comment(0)
F
0

If there are still developers struggling with this issue, here is a slightly more complex but controlled solution:

useState only for what you need to trigger a re-render in the component useRef to track and program the expected behavior

lastScrollY => to keep track of the last scroll value lastScrollGapControl => when greater than 3, resets the value and updates the state, forcing the function to not crazy setStates on every scroll event and cause a bad user experience and bad performance. shortHeader in this case is used to set the short header... so is needed to re-render to apply visual changes.

const handleScroll = () => {
    console.log('scrolling...', window.scrollY, lastScrollY.current);

    if (lastScrollGapControl.current === 0) {
        if (window.scrollY > lastScrollY.current) {
            console.log('scroll down');
            setShortHeader(true);
        } else {
            lastScrollGapControl.current = lastScrollGapControl.current + 1;
            console.log('scroll up');
            setShortHeader(false);
        }
    }

    // remember current page location to use in the next move
    lastScrollY.current = window.scrollY;
    lastScrollGapControl.current = lastScrollGapControl.current + 1;
    if (lastScrollGapControl.current > 3) {
        lastScrollGapControl.current = 0;
    }
};

useEffect(() => {
    console.log('updating syncScroll event listener...');
    window.removeEventListener('scroll', handleScroll);
    window.addEventListener('scroll', handleScroll);

    return () => {
        window.removeEventListener('scroll', handleScroll);
    };
}, []);
console.log('shortHeader re-render', shortHeader);
Fivefinger answered 8/7 at 14:11 Comment(0)
S
-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 />
  )}
</>
Shillelagh answered 15/6, 2023 at 9:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.