Search code examples
javascriptcssreactjseventsonscroll

Add class on Scroll event in React JS


this is my react js code

import React, { useState, useEffect } from "react";
import logo from "../images/logo.png";
import { Link } from "react-scroll";
import $ from "jquery";
import "./Navbar.css";

function Navbar() {
  const [scroll, setScroll] = useState(0);
  useEffect(() => {
    document.addEventListener("scroll", () => {
      const scrollValue = document.documentElement.scrollTop;
      if (scrollValue > 100) {
        const navbar = document.querySelector(".navbar_menu_heade");
        navbar.classList.add("show");
      } else {
        const navbar = document.querySelector(".navbar_menu_heade");
        navbar.classList.remove("show");
      }
    });
  });
  function animation() {
    var tabsNewAnim = $("#navbarSupportedContent ");
    var activeItemNewAnim = tabsNewAnim.find(" .active");
    var activeWidthNewAnimHeight = activeItemNewAnim.innerHeight();
    var activeWidthNewAnimWidth = activeItemNewAnim.innerWidth();
    var itemPosNewAnimTop = activeItemNewAnim.position();
    var itemPosNewAnimLeft = activeItemNewAnim.position();
    $(".hori-selector").css({
      top: itemPosNewAnimTop.top + "px",
      left: itemPosNewAnimLeft.left + "px",
      height: activeWidthNewAnimHeight + "px",
      width: activeWidthNewAnimWidth + "px",
    });


$("#navbarSupportedContent").on("scroll", "li", function (e) {
  $("#navbarSupportedContent ul li ").removeClass("active");
  $(this).addClass("active");
  var activeWidthNewAnimHeight = $(this).innerHeight();
  var activeWidthNewAnimWidth = $(this).innerWidth();
  var itemPosNewAnimTop = $(this).position();
  var itemPosNewAnimLeft = $(this).position();
  $(".hori-selector").css({
    top: itemPosNewAnimTop.top + "px",
    left: itemPosNewAnimLeft.left + "px",
    height: activeWidthNewAnimHeight + "px",
    width: activeWidthNewAnimWidth + "px",
  });
});}



useEffect(() => {
    animation();
    $(window).on("resize", function () {
      setTimeout(function () {
        animation();
      }, 500);
    });
  }, []);

  return (
    <section className="navbar_menu_heade">
      <nav className="navbar navbar-expand-lg navbar-mainbg">
        <Link to="home" offset={-80} className="navbar-brand navbar-logo">
            <img src={logo} alt="logo" className='logoimg' />
          </Link>
        <button
          className="navbar-toggler shadow-none"
          onClick={function () {
            setTimeout(function () {
              animation();
            });
          }}
          type="button"
          data-toggle="collapse"
          data-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <i className="fas fa-bars "></i>
        </button>

    <div className="navbar-collapse collapse" id="navbarSupportedContent">
      <ul className="navbar-nav ml-auto" >
        <div className="hori-selector">
          <div className="left"></div>
          <div className="right"></div>
        </div>
        <li className="nav-item active">
        <Link to="home" spy={true} offset={-80} className="nav-link">
          Home
        </Link>
        </li>

        <li className="nav-item">
          <Link className="nav-link " spy={true} offset={-80} to="about">
            About
          </Link>
        </li>

        <li className="nav-item">
          <Link className="nav-link" spy={true} offset={-80} to="services">
            Services
          </Link>
        </li>
        <li className="nav-item">
          <Link className="nav-link" spy={true} offset={-80} to="career">
            Career
          </Link>
        </li>
        <li className="nav-item">
          <Link className="nav-link" spy={true} offset={-80} to="portfolio">
            Portfolio
          </Link>
        </li>
        <li className="nav-item">
          <Link className="nav-link" spy={true} offset={-80} to="contact">
            Contact
          </Link>
        </li>
      </ul>
    </div>
  </nav>
</section>


);}
export default Navbar;

In this code i have a navbar which has applied css which is following:

    @import url("https://fonts.googleapis.com/css?family=Roboto");

body {
  font-family: "Roboto", sans-serif;
  background: #fff; /* fallback for old browsers */
}
* {
  margin: 0;
  padding: 0;
}
i {
  margin-right: 10px;
}
.navbar-logo {
  padding: 0px;
  color: #dfe2ff;
  margin-right: 46px;
  margin-left: 100px;
}

.logoimg{
  max-width: 75% !important;
}
.fa-bars{
  color: #5161ce;
}

.navbar-mainbg {
  background-color: #dfe2ff;
  padding: 0px;
}
#navbarSupportedContent {
  overflow: hidden;
  position: sticky;
}
#navbarSupportedContent ul {
  padding: 0px;
  margin: 0px;
  margin-left: auto;
}
#navbarSupportedContent ul li a i {
  margin-right: 10px;
}
#navbarSupportedContent li {
  list-style-type: none;
  float: left;
}
#navbarSupportedContent ul li a {
  color:  #5161ce;
  text-decoration: none;
  font-size: 15px;
  display: block;
  padding: 20px 20px;
  transition-duration: 0.6s;
  transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
  position: relative;
}
#navbarSupportedContent > ul > li.active > a {
  color: #5161ce;
  background-color: transparent;
  transition: all 0.7s;
  font-weight: 800;
}
#navbarSupportedContent > ul > li > a.active {
  color: #5161ce;
  background-color: transparent;
  transition: all 0.7s;
  font-weight: 800;
}
#navbarSupportedContent a:not(:only-child):after {
  content: "\f105";
  position: absolute;
  right: 20px;
  top: 10px;
  font-size: 14px;
  font-family: "Font Awesome 5 Free";
  display: inline-block;
  padding-right: 3px;
  vertical-align: middle;
  font-weight: 900;
  transition: 0.5s;
}
#navbarSupportedContent .active > a:not(:only-child):after {
  transform: rotate(90deg);
}

.hori-selector {
  display: inline-block;
  position: absolute;
  height: 100%;
  top: 0px;
  left: 0px;
  transition-duration: 0.6s;
  transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
  background-color: #fff;
  border-top-left-radius: 15px;
  border-top-right-radius: 15px;
  margin-top: 10px;
}
.hori-selector .right,
.hori-selector .left {
  position: absolute;
  width: 25px;
  height: 25px;
  background-color: #fff;
  bottom: 10px;
}
.hori-selector .right {
  right: -25px;
}
.hori-selector .left {
  left: -25px;
}
.hori-selector .right:before,
.hori-selector .left:before {
  content: "";
  position: absolute;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background-color: #dfe2ff;
}
.hori-selector .right:before {
  bottom: 0;
  right: -25px;
}
.hori-selector .left:before {
  bottom: 0;
  left: -25px;
}
@media (max-width: 991px) {
  #navbarSupportedContent ul li a {
    padding: 12px 30px;
  }
  .hori-selector {
    margin-top: 0px;
    margin-left: 10px;
    border-radius: 0;
    border-top-left-radius: 25px;
    border-bottom-left-radius: 25px;
  }
  .hori-selector .left,
  .hori-selector .right {
    right: 10px;
  }
  .hori-selector .left {
    top: -25px;
    left: auto;
  }
  .hori-selector .right {
    bottom: -25px;
  }
  .hori-selector .left:before {
    left: -25px;
    top: -25px;
  }
  .hori-selector .right:before {
    bottom: -25px;
    left: -25px;
  }
}

@media only screen and (max-width: 984px) {
  .navbar-logo {
    padding: 0px;
    color: #dfe2ff;
    margin-right: 46px;
    margin-left: 10px;
    margin-top: 10px;
    margin-bottom: 10px;
  }
  .logoimg{
    max-width: 75% !important;
  }
  .navbar-nav{
    margin-top: 15px !important;
  }
}

First, in this code, I have a navbar that has the Onclick event applied to it, so whenever I click on any navigation on the navbar, it is active, but I also want to add that when I scroll down my page, it will change my active nav-link, which is currently active.

Can anyone assist me in figuring this out?


Solution

  • You can store section names in state and apply active styling to the current section only if it matches the name stored in state. Then using onScroll handler in the parent div, you can change the styling using setState.

    const [activeState, setActiveState] = React.useState('about')
    

    Then define a onScroll handler in the NavBar's parent div

    onScroll={(event) => { 
            const val = event.currentTarget.scrollTop
            const maxVal = event.currentTarget.scrollHeight
            if(val >= maxVal / 5) setActiveState('services')
            else if(val >= 2 * (maxVal / 5)) setActiveState('career')
            else setActiveState('about')
         }}
    

    I've only implemented for 3 sections but I'm sure you can manage the rest.

    NOTE: This implementation assumes that each section is in the parent div and each section has 100% viewport height. I hope you get the intuition however since that's what I'm trying to do here.