가상 스크롤은 전체 1만 개 중 보이는 15개 정도만 DOM에 그립니다. 예를 들어 13번 아이템부터 그려야 한다면, 1~12번 아이템은 DOM에 존재하지 않습니다.
absolute는 상위 요소를 기준으로 dom에 절대좌표 (top)에 배치합니다. 덕분에 dom 순서와 화면상의 위치를 분리할 수 있습니다.
dom에선 첫 자식이지만 화면에서는 900xp지점 배치가 viewport 밖의 좌표라도 상관없이 둘 수 있습니다.
이 absolute의 이 특성을 윈도잉에서 활용합니다.
마치 창문 처럼 렌더링 전체 캔버스에서 절대적인 top 위치에 아이템을 그립니다.
어떤 아이템의 top의 위치가 100인데 scorlltop이 120으로 그 아이템은 화면 위쪽으로 20px 밀려나 viewport에서 벗어난 상태입니다.
이 아이템은 어차피 우리 눈에 보이지 않기 때문에 윈도잉 기법대로 굳이 그리지 않아도 됩니다.
아이템 위치가 viewport 범위 안에 들어온 경우에만 재빠르게 그리면 됩니다.
아래 영상은 마우스의 위치를 스크롤로 제어중이라고 가정했을 때, 실제 가상 스크롤에서 absolute top방식이 영향을 끼치는 것을 눈으로 확인할 수 있는 영상입니다. 만약 저 격자를 전체 viewoport라고 했을때, 스크롤이 된다고 느껴지게끔 하는 것입니다.
absolute 말고 다른 방법은 없을까요? top을 바꾸면 브라우저는 해당 요소의 위치를 레이아웃 단계에서 다시 계산합니다. absolute 요소라 다른 형제 요소까지 밀리는 전체 리플로우를 일으키지 않지만, 레이아웃단계에 관여하게 됩니다. 레이아웃, 페인팅 단계를 최적화를 위해 transform : translateY(top)을 사용해볼수있습니다.
transform은 레이아웃과 페인트 단계를 건너뛰고 마지막 합성(composite) 단계에서만 처리됩니다. 또한 transform이 걸린 요소는 별도 레이어로 승격되기 쉽고, 그 레이어의 합성은 CPU가 아닌 GPU가 담당할 수 있어 성능에 유리합니다.
실제로 TanStack Virtual 공식 문서의 기본 예제도 보이는 아이템을 position: absolute로 두고 transform: translateY(virtualItem.start)로 위치를 잡습니다.
핵심은, absolute + top이든 absolute + transform이든 근본 동작 원리는 같다는 점입니다. 둘 다 요소를 좌표로 배치하는 것이고, transform은 그 중 더 저렴한 단계에서 좌표를 적용하는 최적화일 뿐입니다.
고정 높이의 한계
고정 높이의 계산은 간단한 수학 연산으로 가능할 정도로 단순했습니다.
startIndex = Math.floor(scrollTop / itemSize) // 스크롤 위치 → index
top = index × itemSize // index → 위치
전체 높이 = itemCount × itemSize // 스크롤바 높이
가능했던 이유는 모든 행의 itemSize가 동일하다는 전제가 있었기 때문입니다.
하지만 요즘 리스트는 아이템 높이를 고정으로 둘 수 없는 경우가 많습니다. 아이템 높이를 고정이 아니라면 어떤 문제가 발생할까요?
1. 특정 행의 위치(top)를 즉시 알 수 없다.
7번 행의 위치 (top)를 구하려면 기존방식으로는 7*indexSize(높이)를 계산하면 충분했었습니다.
하지만 높이가 다 다르면, 0 ~ 6번 행의 실제 높이를 전부 알아야 합니다.
2. 스크롤 위치로 시작 인덱스를 즉시 알 수 없다.
아이템 사이즈가 일관되지 않으면 scrollTop/itemSize로 startindex를 구할 수 없습니다.
높이를 하나씩 누적해가면서 scrollTop을 넘는 지점을 찾아야하며, 선형탐색이 필요해집니다.
1만개의 아이템이라면 최악의 경우 1만번 순회하게 됩니다.
sum = 0
for (i = 0; i < itemCount; i++) {
sum += h(i)
if (sum > scrollTop) {
startIndex = i;
break
}
}
3. 전체 높이(스크롤바)를 미리 알 수 없다.
itemCount * itemSize로 구했던 전체 높이도, itemSize가 일관되지 않게 되면, 모든 행의 높이를 알아야합니다.
가장 큰 문제는그리기 전엔 높이를 알 수 없다는 점입니다.
높이를 안다면 누적합으로 가능합니다.
하지만 동적 높이는 각 행의 실제 높이를 DOM에 그려보기 전까진 알 수 없습니다.
내용이 길어지면 줄바꿈이 되어 길어지고, 이미지가 늦게 로드되면 처음의 아이템 높이에서 변경됩니다.
이것이 고정 높이 문제입니다.
고정 높이 가상 스크롤의 개선 방법
문제의 개선 방법은 크게 두 가지 방법이 있습니다.
1. 높이를 미리 알려주기
개발자가 itemSize를 index 높이들을 모두 제공해줍니다.
주어진 높이들을 통해 계산하여 동적 가상 스크롤 구현합니다.
2. 추정 후 측정 및 보정
앞에서 문제였던 부분은 그리기 전엔 높이를 알 수 없다는 점이었습니다.
그렇다면 그리고 난 후 실제 높이를 측정해볼 수 있을까요?
정리
가상 스크롤은 윈도잉을 UI에 적용해, 보이는 것만 그리고 전체 높이는 빈 공간으로 유지하는 기법이다.
고정 높이에서는 scrollTop ÷ itemSize, index × itemSize처럼 위치 계산이 곱셈·나눗셈 한 번으로 끝나 빠르다.
viewport에 보이는 행은 position : absolute로 배치해 DOM 순서와 화면 위치를 분리하고, transform을 쓰면 한단계 더 최적화 할수있다.