사이드 프로젝트

네브바 스크롤에 따라 사라지고 나타나게 하기 (w. React & Tailwind)

개발쉐발 2025. 2. 1. 16:21
728x90
반응형

👉 네브바에 해당 기능을 넣은 이유

일단 네브바가 기본적으로 계속 따라오는 것도 좋지만 아래로 내릴때 화면의 일정 부분을 차지하는게 '포트폴리오' 사이트라는 점에서 시야를 방해하는 요소로 작용할 수도 있다고 생각했다.

 

🛠 적용 방법 및 코드

일단 나는 리액트 + Nextjs + tailwind를 사용하여 프로젝트를 진행 중이고  useState와 useRef, useEffect를 활용해서 구현했다.

  1. 스크롤 위치를 감지 (window.scrollY)
  2. 아래로 스크롤하면 Navbar를 숨기고, 위로 스크롤하면 다시 보이게 설정
  3. useEffect를 이용해서 scroll 이벤트 핸들러 등록
'use client';

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

export default function Navbar() {
  const [isVisible, setIsVisible] = useState(true);
  const lastScrollY = useRef(0); // 리렌더링 방지

  useEffect(() => {
    const handleScroll = () => {
      if (window.scrollY > lastScrollY.current) {
        setIsVisible(false); // 아래로 스크롤하면 숨김
      } else {
        setIsVisible(true); // 위로 스크롤하면 표시
      }
      lastScrollY.current = window.scrollY; // 현재 스크롤 위치 업데이트
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []); // 빈 배열로 설정 → 한 번만 실행되도록

  return (
    <header
      className={`fixed top-0 left-0 w-full bg-[#0F111A] shadow-md z-50 transition-transform duration-300 ${
        isVisible ? "translate-y-0" : "-translate-y-full"
      }`}
    >
      <nav>
       ...
	  </nav>	
}

 

🛠 코드 실행 흐름

1. 초기 상태 설정

const [isVisible, setIsVisible] = useState(true);
const lastScrollY = useRef(0);

 

 

  • isVisible: 요소의 가시성을 관리하는 상태값 (초기값: true → 요소가 보이는 상태).
  • lastScrollY: 이전 스크롤 위치를 저장하는 ref 값 (초기값: 0).
    • useRef(0)을 사용하면 컴포넌트가 리렌더링되어도 값이 유지됨.
    • useState 대신 useRef를 사용하면 값이 변경될 때마다 리렌더링이 발생하지 않음.

2. useEffect 실행 (컴포넌트 마운트 시)

 

useEffect(() => {
  const handleScroll = () => {
    if (window.scrollY > lastScrollY.current) {
      setIsVisible(false); // 아래로 스크롤하면 숨김
    } else {
      setIsVisible(true); // 위로 스크롤하면 표시
    }
    lastScrollY.current = window.scrollY; // 현재 스크롤 위치 업데이트
  };

  window.addEventListener("scroll", handleScroll);

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

 

  • 컴포넌트가 마운트(화면에 나타남)되면 한 번 실행됨 ([] 의존성 배열 덕분).
  • handleScroll 함수를 정의한 후, window 객체에 scroll 이벤트 리스너를 등록.
    • 사용자가 스크롤할 때마다 handleScroll이 실행됨.
  • handleScroll이 실행될 때:
    • 현재 window.scrollY(현재 스크롤 위치)를 lastScrollY.current(이전 위치)와 비교.
    • 아래로 스크롤 (scrollY > lastScrollY.current) → isVisible을 false로 설정해 숨김.
    • 위로 스크롤 (scrollY < lastScrollY.current) → isVisible을 true로 설정해 다시 표시.
    • lastScrollY.current 값을 현재 window.scrollY 값으로 업데이트.

3. return () => window.removeEventListener("scroll", handleScroll); 동작

  • useEffect 내부에서 return으로 반환한 함수는 클린업(clean-up) 함수.
  • 컴포넌트가 언마운트(화면에서 사라짐)되거나, useEffect가 재실행될 때 자동으로 실행됨.
  • 여기서는 scroll 이벤트 리스너를 제거해서 메모리 누수를 방지하는 역할을 함.
    • 만약 이걸 하지 않으면, 컴포넌트가 사라져도 handleScroll이 계속 실행될 수도 있음.

 

🔍 return () => window.removeEventListener("scroll", handleScroll); 자동 실행 원리

  • React에서는 useEffect가 실행될 때, 이전 useEffect의 클린업 함수를 자동으로 실행한 후 새로운 이펙트를 적용.
  • 언마운트 시에도 실행됨 → 그래서 컴포넌트가 사라질 때 removeEventListener가 호출됨.
  • 이 과정 덕분에 이벤트 리스너가 중복으로 등록되지 않도록 막아줌.

👉 쉽게 말하면

  • 처음 마운트될 때 → scroll 이벤트 리스너 등록.
  • 언마운트되면 → scroll 이벤트 리스너 제거.
  • 의존성이 업데이트되면 → 기존 scroll 이벤트 리스너 제거 후 다시 등록.

 

🎯 useRef란?

useRef의 특징

  1. 리렌더링 없이 값을 유지할 수 있음
    • useState를 쓰면 값이 변경될 때마다 컴포넌트가 리렌더링되지만,
      useRef는 값이 변경되어도 리렌더링되지 않음.
  2. .current 속성을 통해 값을 직접 조작 가능
    • useRef는 { current: 값 } 형태의 객체를 반환함.
    • 따라서 lastScrollY.current = window.scrollY; 이렇게 값을 직접 변경할 수 있음.
  3. 초기값을 설정할 수 있음
    • useRef(0)을 사용하면 current 값이 0으로 설정됨.

useRef를 사용하는 이유

  • lastScrollY는 이전 스크롤 위치만 저장하면 되므로, 상태 업데이트를 강제로 트리거할 필요 없음.
  • 만약 useState를 사용하면, 스크롤할 때마다 setLastScrollY(window.scrollY);가 실행되어
    불필요한 리렌더링이 발생함.
  • useRef를 사용하면 리렌더링 없이도 값이 유지되므로 성능이 최적화됨.

 

📌 window.scrollY 값의 변화

window.scrollY는 현재 문서의 최상단에서부터 현재 스크롤 위치까지의 거리(px)를 의미.

  • 아래로 스크롤하면 window.scrollY 값이 커짐.
    → 즉, 스크롤을 내릴수록 window.scrollY 값이 증가.
  • 위로 스크롤하면 window.scrollY 값이 작아짐.
    → 즉, 스크롤을 올릴수록 window.scrollY 값이 감소.

🔍 비교(window.scrollY > lastScrollY.current vs window.scrollY < lastScrollY.current)

const handleScroll = () => {
  if (window.scrollY > lastScrollY.current) {
    setIsVisible(false); // 아래로 스크롤하면 숨김
  } else {
    setIsVisible(true); // 위로 스크롤하면 표시
  }
  lastScrollY.current = window.scrollY; // 현재 스크롤 위치 업데이트
};

 

📌 아래로 스크롤할 때 (window.scrollY > lastScrollY.current)

  1. 현재 window.scrollY 값이 이전보다 커짐.
  2. 즉, 스크롤이 아래로 이동했다는 뜻.
  3. setIsVisible(false)를 실행 → 요소를 숨김.

이유: 스크롤을 내릴 때 요소를 숨기면 화면을 더 넓게 활용할 수 있어서, 네비게이션 바 같은 UI 요소를 감추는 데 자주 사용됨.


📌 위로 스크롤할 때 (window.scrollY < lastScrollY.current)

  1. 현재 window.scrollY 값이 이전보다 작아짐.
  2. 즉, 스크롤이 위로 이동했다는 뜻.
  3. setIsVisible(true)를 실행 → 요소를 다시 표시.

이유: 사용자가 위로 스크롤하는 경우, 보통 네비게이션 바가 다시 보여야 편리하기 때문.


📌 예제: window.scrollY 값 변화 보기

 

  이전 lastScrollY.current 값 현재 window.scrollY 값 비교 결과 isVisible 값
처음 페이지 로드 0 0 = true (보임)
⬇ 아래로 스크롤 (100px) 0 100 > false (숨김)
⬇ 더 아래로 스크롤 (300px) 100 300 > false (숨김)
⬆ 위로 스크롤 (150px) 300 150 < true (표시)
⬆ 더 위로 스크롤 (50px) 150 50 < true (표시)

 

이렇게 동작하는 이유가 window.scrollY 값이 증가하면 아래로 스크롤한 것이고, 감소하면 위로 스크롤한 것이기 때문.

728x90
반응형