[React] ScrollEvent 구현 및 최적화

선유준·2024년 12월 21일
0

REACT

목록 보기
16/16
post-thumbnail

구현하려는 기능

특정 스크롤에 도달하면 스크롤되던 컴포넌트에 fixed를 주는 것

문제

해당 useEffect문에서는 scroll 이벤트를 탐지해서 scrollEvent라는 콜백 함수를 실행시킨다.

정상적으로 작동한다면 console.log가 실행 되어야 하지만, 동작하지 않는다..

원인

const Container = styled.div`	
display: flex;
height: 90vh;  	
overflow-y: auto;
`

스크롤 이벤트를 구현하려는 영역이 위의 Container로 감싸져 있었는데,

스크롤은 화면보다 컨텐츠 요소가 클 경우 발생되기 때문에 높이 자체를 100%나 100vh 로 지정할 경우에도 스크롤 이벤트가 작동되지 않는다.

해결 방법

Container의 height 값을 제거하여 해결할 수 있다

const Container = styled.div`	
  display: flex;
  // height: 90vh; // 제거
  overflow-y: auto;
`

또는 컨텐츠의 높이를 충분히 확보하여 스크롤이 생기도록 할 수 있다

const Container = styled.div`	
  display: flex;
  min-height: 90vh; // height 대신 min-height 사용
  overflow-y: auto;
`

최적화

추가로 스크롤 이벤트를 사용할 때는 이벤트가 빈번하게 발생하므로 최적화를 해주면 좋다.

디바운싱

  1. 마지막 스크롤 이벤트로부터 일정 시간(예: 100ms) 동안 추가 이벤트가 없을 때만 실행
  2. 불필요한 함수 호출을 줄여 성능 개선
  3. API 호출이나 DOM 조작과 같은 무거운 작업이 있을 때 더욱 효과적임
// 디바운싱 적용
useEffect(() => {
  let timeoutId: NodeJS.Timeout;
  
  const scrollEvent = () => {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    
    timeoutId = setTimeout(() => {
      console.log('scroll event!');
      // 스크롤 관련 로직
    }, 100);
  };

  window.addEventListener('scroll', scrollEvent);
  return () => window.removeEventListener('scroll', scrollEvent);
}, []);

IntersectionObserver 활용

  1. 스크롤 이벤트 대신 IntersectionObserver를 사용하면 더 효율적으로 구현가능
  2. 스크롤 이벤트와 달리 요소가 뷰포트에 들어오거나 나갈 때만 콜백이 실행되어 성능적 이점이 있음
useEffect(() => {
  const observer = new IntersectionObserver(
    ([entry]) => {
      // entry.isIntersecting: 요소가 뷰포트와 교차하는지 여부
      // entry.intersectionRatio: 요소가 뷰포트와 교차하는 비율
      // entry.boundingClientRect: 요소의 경계 정보
      if (entry.isIntersecting) {
        // 요소가 뷰포트에 들어왔을 때의 로직
      } else {
        // 요소가 뷰포트에서 벗어났을 때의 로직
      }
    },
    { threshold: 0.5 } // 50% 이상 보일 때 콜백 실행
 // { threshold: [0, 0.5, 1] } // 0%, 50%, 100% 교차 시 콜백 실행
 // rootMargin 옵션으로 감지 영역을 확장하거나 축소할 수 있음.
  );

  if (targetRef.current) {
    observer.observe(targetRef.current);
  }

  return () => observer.disconnect();
}, []);

실제 사용 예시

// 스크롤에 따른 헤더 고정 예시
function Header() {
  const headerRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) {
          // 헤더가 뷰포트를 벗어나면 fixed 스타일 적용
          headerRef.current.classList.add('fixed');
        } else {
          // 헤더가 뷰포트에 들어오면 fixed 스타일 제거
          headerRef.current.classList.remove('fixed');
        }
      },
      { threshold: 0 }
    );

    if (headerRef.current) {
      observer.observe(headerRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return <header ref={headerRef}>...</header>;
}

정리

스크롤 이벤트를 제대로 감지하기 위해서는 컨테이너의 높이 설정이 중요합니다.

단순히 뷰포트 높이를 기준으로 설정하면 스크롤이 발생하지 않을 수 있으므로, 실제 컨텐츠의 높이를 고려하여 설정해야 합니다.

또한, 성능 최적화를 위해 디바운싱이나 IntersectionObserver와 같은 기술을 활용하면 더 효율적인 구현이 가능합니다.

profile
매일매일 발전하는 개발자를 목표로!

0개의 댓글