행이 수천~수만 개인 목록을 전부 DOM에 그리면 브라우저가 버벅입니다. 가상 리스트는 스크롤 위치를 보고 지금 화면에 보이는 몇 개(+여유분 버퍼)만 실제 DOM 요소로 그리고, 나머지는 존재하지 않는 것처럼 취급합니다. 전체 목록의 스크롤 길이를 흉내 내려고 보이지 않는 "자리표시자" 컨테이너를 실제 전체 높이(행 수 × 행 높이)로 만들어 두고, 그 안에서 보이는 행들만 translateY로 제자리에 배치합니다.
아래 데모의 카운터가 스크롤해도 10~20 근처에 머무는 게 핵심입니다 — 목록 데이터는 10,000행이지만 DOM에는 항상 화면에 들어오는 만큼만 존재합니다. 페이지네이션과 비교하면, 페이지네이션은 사용자에게 몇 번째 "페이지"인지 명시적으로 보여주고 클릭으로 이동하지만, 가상 리스트는 페이지 구분 없이 하나로 이어진 목록처럼 매끄럽게 스크롤되면서 내부적으로만 행을 교체합니다.
행 높이가 제각각이면(가변 높이) 구현이 훨씬 까다로워집니다 — 스크롤 위치만으로 몇 번째 행인지 계산할 수 없어 각 행의 실측 높이를 캐싱하는 별도 로직이 필요합니다. 스크린리더 사용자를 위해서는 컨테이너에 aria-setsize/aria-posinset처럼 전체 개수와 지금 위치를 알리는 처리를 더하는 게 좋습니다.