일반적인 스크롤 방식으로 하는 인피니티 스크롤 방법은 엘리먼트의 scrollHeight
, clientHeight
, offsetHeight
, scrollTop
을 활용하여 이벤트를 처리한다.
scrollHeight
엘리먼트의 총 높이를 나타내며 바깥으로 넘쳐서 보이지 않는 콘텐츠도 포함한다.
clientHeight
엘리먼트의 내부 높이를 나타낸다. padding, scroll bar 높이, margin, border를 포함하지 않는다.
offsetHeight
padding포함, scroll bar 높이, margin, border를 포함한 높이이다.
scrollTop
스크롤바의 top 부분이 화면에 내려온 위치
즉, const height = scrollHeight - clientHeight - scrollTop
이 미리 정해놓은 offset 미만일 때 스크롤이 최하단에 왔다고 판단해서 다음 데이터를 가져오고 기존 항목에 덧붙인다. 보통 offset의 크기는 50내외를 적용한다.
스크롤을 움직일 때마다 이벤트가 발생하기 때문에 성능 문제가 야기될 수 있다. 이를 해결하기 위해 보통 스크롤 이벤트에 쓰로틀링(throttling) 혹은 디바운싱(debouncing)을 적용하여 이벤트를 제한한다. 주로 사용하는 방법은 쓰로틀링이다.
이벤트를 일정한 주기마다 발생하는 기술이다. 마지막 함수가 호출된 후 일정 시간이 지나기 전엔 다시 호출되지 않도록 한다.
debounce는 이벤트를 그룹핑해서 특정 시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술이다. 연달아서 호출되는 함수들 중 마지막 함수만 호출하도록 한다.
const refUl = useRef<HTMLUListElement | null>(null);
const curPageRef = useRef<number>(0);
const [isScrollBottom, setIsScrollBottom] = useState<boolean>(false);
.
.
.
// scroll 이벤트에 적용할 함수. refUl 엘리먼트의 ref를 가져와 인피니티 스크롤에 필요한 높이 값들을 가져옴.
// 이후 throttle 효과로 1초 주기마다 발생하도록 작동
const handleScroll = throttle(1000, () => {
if (refUl.current) {
const { scrollHeight, offsetHeight, scrollTop } = refUl.current;
const offset = 50;
setIsScrollBottom(scrollHeight - offsetHeight - scrollTop < offset);
}
});
.
.
.
// isScrollBottom이 될 때마다 useEffect()호출, 현재 페이지 수 증가,
// 마지막 데이터가 아닐 경우에만 fetch 호츌
useEffect(() => {
if (isScrollBottom) {
curPageRef.current = curPageRef.current + 1;
!isLast && getFetchData();
}
}, [isScrollBottom, isLast]);
.
.
.
// 페이지의 기본 이벤트에 있는 onScroll에 함수를 넣어주어 스크롤 이벤트가 발생하면 호출되도록 함
return (
<ul ref={refUl} onScroll={handleScroll}>
{
// fetch해온 데이터 로직 작성...
}
</ul>
);