원했던 기능

  1. 유저가 스크롤을 내릴 때도 상단에 고정되는 헤더를 만들고 싶었다.
  2. 원래 헤더는 투명으로 하고, 브라우저에서 스크롤을 내리고 있는 동안 헤더가 잘보이도록 헤더의 배경색을 하얀색으로 바꿔주고 싶었다.

 

Window.scrollY를 사용해서 스크롤되고 있는지를 감지

Window.scrollY - Web API | MDN

scrollY는 읽기 전용 속성으로, 문서가 수직으로 얼마나 스크롤됐는지 픽셀 단위로 반환한다.

updateScroll 함수를 만들어서 scrollY가 75 (=헤더의 높이) 이상이면 현재 스크롤되고 있는 것으로 판단해서 scrollFlag를 true로 한다. 여기서 scrollFlag는 useState를 활용한다.

 

HeaderContainer에 isScroll 변수명으로 props를 만들고 scrollFlag를 넣는다.

scrollFlag의 초기값은 false로 하고, 스크롤이 감지되면 true로 업데이트하고 리렌더링 한다.

 

scrollFlag가 false일 땐 배경색을 투명으로 하고, true일 땐 배경색을 흰색으로 하고 밑테두리 css를 추가했다.

또한 상태 전환 시 transition으로 끊김이 없어보이도록 변화를 부드럽게 만들었다.

const [scrollFlag, setScrollFlag] = useState(false);

 const updateScroll = () => {
    const { scrollY } = window;
    const scrolled = scrollY > 75;

    setScrollFlag(scrolled);
  };

<HeaderContainer isScroll={scrollFlag}></HeaderContainer>

const HeaderContainer = styled.header`
  position: fixed;
  width: 100%;
  height: 75px;
  background-color: transparent;
  color: #fff;
  z-index: 9999;
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;

  transition: all 0.8s ease-out;
  ${(props) =>
    props.isScroll &&
    css`
      background-color: #ffffff;
      border-bottom: 1px solid rgba(0, 0, 0, 0.3);
    `};
`;

 

그래서 window.scrollY로 스크롤이 발생할 때마다 state를 업데이트하면 되겠다는 생각까진 도달했다.


 

하지만 scroll 이벤트는 유저가 스크롤을 조금만 움직여도 실행되기 때문에 scroll을 할 때마다 state를 변경하려는 생각은 잠시 접어두자… 해당 컴포넌트가 수십번 불필요하게 리렌더링되기 때문이다.

 

scroll 이벤트를 제어하기 위해서는 일정 시간 걸어두고 그 안에서 이벤트를 한 번만 실행되도록하는 throttle을 적용하는 게 좋겠다. 여러번 발생하는 이벤트를 일정 시간 동안 한 번만 실행시키는 게 적합해 보이기 때문이다.

 

throttle 만들기

const throttle = (callback, delay) => {
    let timer = null;
    return () => {
      if (timer) return;
      timer = setTimeout(() => {
        callback();
        timer = null;
      }, delay);
    };
};
  1. timer를 담을 변수를 정의
  2. 존재 여부에 따라 setTimeout 함수를 실행시킨다.
    1. timer가 없다면 setTimeout이 실행되지 않은 상태이므로, setTimeout 함수를 실행시키며 정해진 시간 후에 callback 함수를 실행시키고 timer를 다시 비워준다
    2. timer가 있다면 그대로 리턴한다.

 

src/components/Header.jsx

import { useEffect, useState } from "react";
import { Link as L, useLocation } from "react-router-dom";
import styled, { css } from "styled-components";
import { logo } from "assets/img";

function Header() {
  let { pathname } = useLocation();
  const [scrollFlag, setScrollFlag] = useState(false);

  const throttle = (callback, delay) => {
    let timer = null;
    return () => {
      if (!timer) {
        timer = setTimeout(() => {
          callback();
          timer = null;
        }, delay);
      }
    };
  };

  const updateScroll = () => {
    const { scrollY } = window;
    const scrolled = scrollY > 75;

    setScrollFlag(scrolled);
  };

  const handleScroll = throttle(updateScroll, 100);

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

  return (
    <HeaderContainer isScroll={scrollFlag}>
      <Link to="/">
        <img src={logo} alt="LOGO" />
      </Link>

      <nav>
        <NavContainer>
          <li>
            <Link to="/intro" open={pathname === "/intro" && true}>
              Intro
            </Link>
          </li>
          <li>
            <Link to="/crime" open={pathname === "/crime" && true}>
              금융 사기
            </Link>
          </li>
          <li>
            <Link to="/prevent" open={pathname === "/prevent" && true}>
              범죄 예방
            </Link>
          </li>
        </NavContainer>
      </nav>
    </HeaderContainer>
  );
}

export default Header;

 

 

https://velog.io/@dami/ReactThrottle%EB%A1%9C-Sticky-Header-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

+ Recent posts