패턴

[Virtual Scroll] 1만 개 리스트도 보이는 15개만 그려 부드럽게 스크롤

항목이 수만 개여도 화면에 보이는 만큼만 실제 DOM으로 그리는 windowing 성능 패턴. 라이브러리 없이 직접 구현.

ReactTypeScriptscrollToptransform translateYuseMemo
라이브 데모
새 탭에서 열기
데모 불러오는 중…

제작 과정

한 줄 요약 항목이 1만 개여도 화면에 보이는 ~15개만 진짜로 그리고, 스크롤할 때 그 몇 개를 재활용해 위치만 옮기는 기법입니다.

이럴 때 필요해요

채팅 메시지, 검색 결과, 표처럼 항목이 수천~수만 개로 늘어나는 화면을 만들 때입니다. 1만 개를 그냥 다 그리면 브라우저가 DOM 노드(화면을 구성하는 요소 하나하나)를 1만 개나 만들어야 해서 스크롤이 뚝뚝 끊기고 메모리도 폭발합니다. 가상 스크롤(virtual scroll, 일명 windowing)은 "어차피 한 번에 보이는 건 십몇 개뿐인데 왜 1만 개를 다 그리지?"라는 발상에서 출발합니다.

어떻게 동작하나

  1. 행 높이를 고정(예 56px)으로 정해두면 스크롤한 거리(scrollTop)만 보고 "지금 맨 위에 걸친 게 몇 번째 항목인지"를 나눗셈으로 바로 알 수 있습니다.

  2. 빈 spacer div의 높이를 전체 개수 행 높이로 만들어 둡니다 실제로는 몇 개만 그려도 스크롤바는 "진짜 1만 개짜리" 길이로 보입니다.

  3. 보이는 구간의 항목만 잘라 그리고, 그 묶음을 translateY로 제자리까지 한 번에 밀어 둡니다. overscan(여유분, 위아래로 몇 개 더 그려두는 것)을 두어 빠르게 스크롤해도 빈칸이 안 보이게 합니다.

핵심은 이거예요

화면 카운터에 "실제 렌더된 DOM 행"이 전체 개수와 상관없이 항상 ~15개로 유지되는 것 이것만 이해하면 됩니다. 나머지는 그 15개의 위치를 계산해 옮기는 일일 뿐입니다.

놓치기 쉬운 것

  • 행 높이가 들쭉날쭉하면 이 단순 계산이 깨집니다(가변 높이는 측정누적합이 추가로 필요).

  • overscan이 0이면 빠른 스크롤 시 잠깐 빈칸이 보입니다.

  • spacer 높이를 안 만들면 스크롤바가 짧아져 끝까지 스크롤이 안 됩니다.

이런 곳에 써요

  • 슬랙디스코드 같은 긴 메시지 타임라인

  • 수천 행짜리 관리자 테이블, 무한 스크롤 피드

소스 코드

· 데모 페이지에서 자동 추출
import type { Metadata } from "next";
import { VirtualListDemo } from "./-components/VirtualListDemo";

export const metadata: Metadata = {
  title: "가상 스크롤 리스트 (데모)",
  description:
    "1만 개 이상의 큰 리스트도 화면에 보이는 항목(~15개)만 실제로 그려서 부드럽게 스크롤하는 windowing(가상 스크롤) 성능 패턴. 라이브러리 없이 직접 구현 — 실제 렌더된 DOM 행 수를 실시간 카운터로 체감.",
  robots: { index: false, follow: false },
};

export default function Page() {
  return (
    <main className="flex min-h-[100dvh] items-center justify-center bg-gradient-to-br from-slate-50 via-white to-indigo-50/40 p-6">
      <VirtualListDemo />
    </main>
  );
}
21조회수

댓글