[React] Down 스크롤 감지로 애니메이션 추가 하기!

이원찬·2024년 6월 28일

React

목록 보기
12/17

현재 아래와 같이 스크롤을 감지 하여 아래로 스크롤 될때는 정보를 가리지 않게 네베게이션 바가 아래로 내려가고 스크롤이 올라갈때는 네비게이션 바가 올라오는 효과를 React로 구현하고 싶었다.

참고한 사이트

오늘 수산시장의 해산물 시세를 한번에, 인어교주해적단

1️⃣ useEffect로 스크롤 이벤드 감지하기

일단 스크롤 이벤트 마다 특정 함수가 실행 되어야 한다고 생각했다.

특정 이벤트마다 실행되는 함수를 정의할때는 useEffect hook 을 이용하면 된다고 생각하여 바로 시작했다.

useEffect(() => {
  const handleScroll = () => {
    console.log("scrolling"); // 스크롤이 감지되면 로깅!
  };

  window.addEventListener("scroll", handleScroll);

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

스크롤 감지는 되었고 스크롤시 현재 스크롤 위치를 로깅하는 함수 바꾸어 주었다.

useEffect(() => {
  const handleScroll = () => {
    console.log(document.documentElement.scrollTop); // 뷰의 최상단 위치를 로깅!
  };

  window.addEventListener("scroll", handleScroll);

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

위처럼 document.documentElement.scrollTop 를 확인하면 스크롤이 최상단에 위치할때 0 이 찍히는 것을 볼수 있다.

2️⃣ 이전 스크롤 위치와 비교하여 내려가는지, 올라가는지 확인하기

여기서 handleScroll 함수가 실행 됬을때 이전 스크롤과 현재 스크롤을 비교 했을때

  • 현재 스크롤이 크다 ? 내려가는중!
  • 현재 스크롤이 작다 ? 올라가는중!

으로 조건문 처리하여 상태를 저장해 보았다.

내려가는중인지 판단 하기 위해 isScrollDowning 상태와 이전 스크롤 위치를 저장할 lastTopScroll 상태를 선언하였다.

const [isScrollDowning, setIsScrollDowning] = useState(false);
const [lastTopScroll, setLastTopScroll] = useState(
  document.documentElement.scrollTop
);

useEffect(() => {
  const handleScroll = () => {
    const currentTopScroll = document.documentElement.scrollTop;

    if (currentTopScroll > lastTopScroll) {
      setIsScrollDowning(true);
    } else {
      setIsScrollDowning(false);
    }

    setLastTopScroll(currentTopScroll);
  };

  window.addEventListener("scroll", handleScroll);

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

❗문제 발생

위 코드는 문제가 있다.

바로 lastTopScroll 상태를 useEffect 에서 사용하면서 의존성 배열에 넣지 않았기 때문인데

내가 원하던 동작은 스크롤 이벤트가 감지됐을 때 이전 스크롤 위치와 현재 스크롤 위치를 비교하여 동작하는 것인데 위 lastTopScroll 는 클로저에 갇혀 버려 더이상 업데이트 되지 않는 상수 처럼 취급 되기 때문이다.

handleScroll에서 `console.log(lastTopScroll);` 를 찍어보면 간단히 확인 가능하다.

Untitled

아무리 스크롤해도 바뀌지 않는 모습…

✅ 해결 방법

간단하다 아래와 같이 의존성 배열에 useEffect 안에서 사용중인lastTopScroll 상태를 추가 해주자

클로저로 lastTopScroll 변수가 갇히지만 변할때마다 다시 함수를 만들어 준다면 클로저에 갇혀도 상관없을 것이다.

const [isScrollDowning, setIsScrollDowning] = useState(false);
const [lastTopScroll, setLastTopScroll] = useState(
  document.documentElement.scrollTop
);

useEffect(() => {
		...
    return () => window.removeEventListener("scroll", handleScroll);
}, [lastTopScroll]); // 여기 추가!

❗주의점

만약 useEffectreturn 에 (언마운트시 동작할 함수) 이벤트 리스너를 제거하지 않으면 lastTopScroll 이 변경되며 수많은 리렌더링시 이벤트 리스너가 너무 많아 성능이 저하될 것이다!

🤔 추가 기능 구현 offset 으로 자잘한 감지 막기

화면 기록 2024-06-28 오전 11.25.05.gif

현재 구현한 기능은 자잘한 스크롤 (1px) 만이라도 움직이면 상태가 변경되어 navbar가 올라갔다 내려갔다 한다…

내가 참고한 사이트 에서는 이렇게 동작 하지 않았다…

offset 변수를 설정하여 특정 움직이 이하일 때는 감지를 막아보자!

사실 이건 정말 간단하다 offset 상수를 설정하고 if문으로 early return 으로 처리하면 되기 때문이다

useEffect(() => {
  const handleScroll = () => {
    const currentTopScroll = document.documentElement.scrollTop;

    // offset 만큼의 변화가 없다면 무시!
    if (Math.abs(currentTopScroll - lastTopScroll) < offset) return;

    if (currentTopScroll > lastTopScroll) {
      setIsScrollDowning(true);
    } else {
      setIsScrollDowning(false);
    }

    setLastTopScroll(currentTopScroll);
  };

  window.addEventListener("scroll", handleScroll);

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

이제 참고한 사이트와 똑같이 기능이 동작한다!

profile
소통과 기록이 무기(Weapon)인 개발자

0개의 댓글