Intersection Observer를 이용한 뷰포트 감지

Jina·2025년 11월 7일
post-thumbnail

웹사이트를 둘러보다 보면 스크롤할 때 특정 요소가 화면에 나타나면 애니메이션이 실행되거나 데이터가 로드되는 경험을 한 적이 있을 것이다. 또는 무한 스크롤로 자동으로 더 많은 콘텐츠가 로드되는 페이지가 있다. 이런 기능들은 Intersection Observer API를 활용하면 쉽게 구현할 수 있다.


1. Intersection Observer란?

Intersection Observer는 어떤 요소가 화면에 보이는지 감지하는 역할을 하는 JavaScript API다.

사용자가 페이지를 스크롤할 때, 특정 요소가 화면(viewport)에 들어왔는지 나갔는지를 자동으로 감지해준다. 이때 원하는 작업을 실행할 수 있다.

아래와 같은 상황에 쓰인다.

  • 이미지가 화면에 보일 때만 로드하기 (성능 최적화)
  • 특정 섹션이 보일 때 애니메이션 시작하기
  • 무한 스크롤 구현하기
  • 사용자가 어느 섹션을 보고 있는지 추적하기

2. 기본 개념

Intersection Observer를 사용하려면 세 가지만 이해하면 된다.

1) Observer 생성하기

const observer = new IntersectionObserver(callback, options);

2) 감시할 요소 등록하기

observer.observe(element);

3) 콜백 함수에서 반응하기

const callback = (entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('요소가 화면에 보입니다!');
    }
  });
};

3. React에서 실제로 구현해보기

기본 예제: 섹션이 보일 때 메시지 출력

import { useEffect, useRef } from 'react';

function ScrollDetectionDemo() {
  const sectionRef = useRef(null);

  useEffect(() => {
    // 1. 콜백 함수 정의
    const handleIntersection = (entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          console.log('섹션이 화면에 보입니다!');
        } else {
          console.log('섹션이 화면을 벗어났습니다');
        }
      });
    };

    // 2. Observer 생성
    const observer = new IntersectionObserver(handleIntersection);

    // 3. 요소 감시 시작
    if (sectionRef.current) {
      observer.observe(sectionRef.current);
    }

    // 4. 컴포넌트 언마운트 시 정리
    return () => {
      if (sectionRef.current) {
        observer.unobserve(sectionRef.current);
      }
    };
  }, []);

  return (
    <div>
      <div style={{ height: '100vh', background: 'white' }}>
        <h1>위로 스크롤하세요</h1>
      </div>
      
      <div ref={sectionRef}>
        여기가 감지 대상입니다!
      </div>

      <div style={{ height: '100vh', background: 'white' }}>
        <h1>아래로 스크롤하세요</h1>
      </div>
    </div>
  );
}

export default ScrollDetectionDemo;

4. 실용적인 예제 - 섹션별 감지

여러 섹션을 동시에 감지하기

import { useEffect, useRef } from 'react';

function MultiSectionDetection() {
  const section1Ref = useRef(null);
  const section2Ref = useRef(null);
  const section3Ref = useRef(null);

  useEffect(() => {
    const handleIntersection = (entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          // 보이는 요소에 'visible' 클래스 추가
          entry.target.classList.add('visible');
        } else {
          entry.target.classList.remove('visible');
        }
      });
    };

    // 옵션 설정
    const options = {
      threshold: 0.3 // 요소의 30%가 보일 때 감지
    };

    const observer = new IntersectionObserver(handleIntersection, options);

    // 모든 섹션 감시
    [section1Ref, section2Ref, section3Ref].forEach(ref => {
      if (ref.current) {
        observer.observe(ref.current);
      }
    });

    return () => {
      [section1Ref, section2Ref, section3Ref].forEach(ref => {
        if (ref.current) {
          observer.unobserve(ref.current);
        }
      });
    };
  }, []);

  const sectionStyle = {
    height: '400px',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: '32px',
    fontWeight: 'bold',
    margin: '100px 0',
    opacity: 0.5,
    transition: 'all 0.6s ease-out',
    borderRadius: '10px'
  };

  return (
    <div style={{ padding: '20px' }}>
      <h1>섹션 감지 데모</h1>
      
      <div ref={section1Ref} style={{ ...sectionStyle, background: '#FF6B6B' }}>
        섹션 1
      </div>
      <style>{`
        .visible {
          opacity: 1 !important;
          transform: translateY(0);
        }
      `}</style>

      <div ref={section2Ref} style={{ ...sectionStyle, background: '#4ECDC4' }}>
        섹션 2
      </div>

      <div ref={section3Ref} style={{ ...sectionStyle, background: '#FFE66D' }}>
        섹션 3
      </div>
    </div>
  );
}

export default MultiSectionDetection;

5. 옵션 이해하기

Intersection Observer에는 다양한 옵션이 있다.
초보자가 꼭 알아야 할 욥션 3가지를 알아보자.

const options = {
  // 1. threshold: 감지 시점 설정 (0~1 사이)
  threshold: 0.5, // 요소의 50%가 보일 때 감지
  
  // 2. root: 감지 범위 설정
  root: null, // null이면 브라우저 뷰포트 기준
  
  // 3. rootMargin: 감지 범위 여백 조정
  rootMargin: '50px' // 화면에서 50px 전에 감지 시작
};

예시

// 요소가 화면에 조금만 들어오면 감지
const observer = new IntersectionObserver(callback, { threshold: 0.1 });

// 요소가 완전히 보일 때만 감지
const observer = new IntersectionObserver(callback, { threshold: 1 });

// 요소가 화면에 보이기 100px 전부터 감지
const observer = new IntersectionObserver(callback, { rootMargin: '100px' });

6. 실전 팁: 성능 최적화

1) 불필요한 렌더링 방지

const [isVisible, setIsVisible] = useState(false);

const handleIntersection = (entries) => {
  entries.forEach(entry => {
    // 상태가 바뀔 때만 업데이트
    if (entry.isIntersecting && !isVisible) {
      setIsVisible(true);
    }
  });
};

2) 한 번만 감지하고 관찰 중단하기

const handleIntersection = (entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 할 일 처리
      observer.unobserve(entry.target);
    }
  });
};

3) threshold값 조절

  • 값이 크면(0.8 이상): 요소가 대부분 보일 때 반응 → 더 정확하지만 민감함
  • 값이 작으면(0.1 이하): 요소가 조금만 보여도 반응 → 더 빠르게 반응
profile
즐겁게 코딩하고 공부해요🍀

0개의 댓글