무한 스크롤(Intersection Observer) 정리 ✍️

Yunsung·2025년 6월 7일
post-thumbnail

최근 프로젝트에서 무한 스크롤 기능을 구현하게 됐다.
여러 방법을 비교해본 끝에 최종적으로 Intersection Observer를 사용했고, 만족스러워서 기록 겸 정리해본다.

✅ 무한 스크롤 구현 방식과 비교

1. window.onscroll 직접 사용

window의 스크롤 이벤트를 감지해서,
스크롤이 바닥 근처에 도달하면 데이터를 추가로 불러오는 방식.

장점

  • 거의 모든 브라우저에서 동작

  • 구현이 단순하고 직관적

단점

  • 스크롤 이벤트가 너무 자주 발생 → 성능 이슈
    → 쓰로틀링(throttling)이나 디바운싱(debouncing)이 필수

  • 여러 스크롤 영역(모달, 특정 div 등)에 적용하려면 관리가 복잡

  • 코드가 지저분해지기 쉬움


2. Intersection Observer API 사용

특정 DOM 요소가 화면에 보이는지 여부를 감지해서,
화면에 나타나면 데이터를 추가로 로드하는 방식.

장점

  • 브라우저 레벨에서 최적화됨 → 성능 매우 우수

  • 코드가 간결하고 유지보수에 유리

  • React 같은 프레임워크와 궁합이 좋음

단점

  • 구형 브라우저(IE 등)에서는 미지원

  • 관찰 대상 요소 위치, threshold 등 세부 조정이 필요


3. 외부 라이브러리 사용

(예: react-infinite-scroll-component, react-intersection-observer 등)

위 두 방식을 내부적으로 캡슐화한 컴포넌트/훅 형태로 제공.
더 쉽게 사용할 수 있도록 추상화돼 있음.

장점

  • 코드가 매우 간결

  • 로딩/에러 처리, 마지막 페이지 감지 등 부가기능이 잘 갖춰져 있음

단점

  • 프로젝트에 따라 무거울 수 있음

  • 커스터마이징이 제한적일 수 있음

🔍 내가 선택한 방식: Intersection Observer

이번 프로젝트에서는 Intersection Observer API를 직접 사용해서 구현했다.
코드도 깔끔하고 성능도 좋아서, 앞으로도 이 방식으로 구현할 듯하다.

✅ 기본 문법

const observer = new IntersectionObserver(callback, options);
observer.observe(targetElement);
  • callback: 대상 요소가 화면에 들어오거나 나갈 때 실행되는 함수
  • options: 감지 기준을 설정하는 옵션 객체
  • observe(): 감지할 대상 DOM 요소 지정

🔧 옵션 설명

  • root: 기준이 되는 요소 (null이면 브라우저 뷰포트 기준)
  • threshold: 요소가 얼마나 보여야 감지할지 비율 (0.0 ~ 1.0)
  • rootMargin: root 기준 여백 설정 (ex: "0px 0px 100px 0px" → 아래쪽 100px 미리 감지)

💡 예제 코드 (옵션 설명 포함)

import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";

// 더미 데이터 100개 생성 (id, 제목, 이미지)
const dummy = Array.from({ length: 100 }, (_, i) => ({
  id: i + 1,
  title: `카드 ${i + 1}`,
  img: `https://picsum.photos/seed/${i + 1}/200/120`,
}));

function BlogInfiniteScrollDemo() {
  // 한 번에 보여줄 카드 개수 상태
  const [visible, setVisible] = useState(8);
  // loader div를 참조할 ref
  const loader = useRef(null);

  useEffect(() => {
    // Intersection Observer로 loader가 화면에 보이면 카드 추가
    const io = new IntersectionObserver(
      ([e]) => {
        if (e.isIntersecting) setVisible((v) => Math.min(v + 8, dummy.length));
      },
      { threshold: 0.2 } // loader가 20% 보이면 트리거
    );
    if (loader.current) io.observe(loader.current);
    return () => io.disconnect(); // 언마운트 시 observer 해제
  }, []);

  return (
    <Wrap>
      {/* 카드 리스트 (grid) */}
      <Grid>
        {dummy.slice(0, visible).map((item) => (
          <CardBox key={item.id}>
            <Img src={item.img} alt="" />
            <Title>{item.title}</Title>
          </CardBox>
        ))}
      </Grid>
      {/* loader: 화면에 보이면 추가 카드 로드 */}
      <Loader ref={loader}>
        {visible < dummy.length ? "불러오는 중..." : "끝"}
      </Loader>
    </Wrap>
  );
}

export default BlogInfiniteScrollDemo;

🧠 요약 포인트

  • observe(): 감지할 요소 등록
  • isIntersecting: true면 요소가 화면 안에 들어왔다는 의미
  • threshold: 감지 민감도 설정 → 0이면 살짝 보이기만 해도 감지
  • rootMargin: 감지 범위를 키워서 프리로딩 효과 가능

영상

profile
풀스택 개발자로서의 도전을 하는 중입니다. 많은 응원 부탁드립니다!!😁

0개의 댓글