| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- CSS
- 개발
- Pr
- 가상 스크롤
- es module
- JWT
- 회고록
- 티스토리챌린지
- 동시성 모드
- EVENT
- 광주탈렌트페스티벌
- react-query
- 회고
- Git
- javascript
- 협업
- JS 코딩의 기술
- Fe
- Study
- js
- 오블완
- typescript
- next.js
- 개발자
- 일관리
- github
- react
- 광탈페
- frontend
- form
- Today
- Total
ougi FE
무한 스크롤과 가상 스크롤에 대해서 알아보자 본문
오늘은 저희 팀 친구가 정말 가볍고 좋은 무한 스크롤 그리고 가상 스크롤 라이브러리를 내서
저희 프로젝트에 적용 하는 겸 이렇게 무한 스크롤과 가상 스크롤에 대해서 글을 쓰게 되었습니다

무한 스크롤이란?

무한스크롤이란 말 그대로 연속적인 스크롤을 제공하는 UI/UX 요소입니다
화면에 표시된 아이템 목록의 끝까지 내려갔을 때 자동으로 다음 데이터를 요청하여 추가 아이템을 불러오는 기술입니다
페이지네이션과의 차이는 페이지네이션은 다음 페이지로 넘어갈 때 api가 호출되어 데이터를 불러오지만
무한 스크롤은 스크롤할 때마다 api가 데이터를 불러옵니다
한 번에 모든 데이터를 불러오는 것이 아니라서 큰 장점이 있습니다
적용 사례는 유튜브, 인스타그램 등 많은 소셜 미디어에 적용되어 있습니다
하지만 페이지에 과도한 요소가 추가되면 브라우저 성능이 저하되어 페이지 로딩에 실패하거나 브라우저가 다운될 수 있습니다
-> 1500개 이하 권장
장점
- 무한스크롤은 사용자가 콘텐츠를 끊김 없이 탐색할 수 있습니다 자연스러운 사용자 경험 제공
- 스크롤만으로 추가 콘텐츠가 로드되기 때문에 사용자가 더 많은 콘텐츠를 소비하게 유도합니다
- 스크롤만으로 콘텐츠 탐색이 가능해 사용자에게 편리함을 제공합니다
단점
- 지나치게 많은 스크롤을 요구할 수 있습니다
- 사용자의 기기 성능과 네트워크 속도에 따라 느려질 수 있습니다
- 특정 콘텐츠에 바로 접근하기 어려워지고 원하는 정보를 찾는 것에 어려움이 생깁니다
무한 스크롤 적용하는 방법
Scroll Event
사용자가 스크롤을 할 때 특정 위치에 도달하면 새로운 데이터를 자동으로 불러오는 방식입니다
스크롤 이벤트를 통해 현재 스크롤 위치를 감지하고 페이지의 끝에 가까워지면 데이터를 추가로 로드합니다
비교적 간단하게 구현 가능하지만 성능상 좋지 않을 수 있습니다
Intersection Observer API
특정 요소가 뷰포트에 나타나는 시점을 감지하고 해당 시점에 새로운 데이터를 로드하는 방식입니다
요소와 뷰포트 간의 교차를 비동기적으로 관찰할 수 있고 스크롤 성능에 미치는 영향을 줄일 수 있습니다
이 방법은 최신 브라우저에서 지원됩니다
가상 스크롤이란?

유저 입장에서 직접 보이는 요소들만 렌더링하고 보이지 않는 요소들은 렌더링하지 않는 기술입니다
아이템이 너무 많을 때 모든 요소를 렌더링하고 있으면 성능이 분명히 안좋아질 것입니다
이럴 때 필요한 것이 바로 가상 스크롤입니다
대용량 데이터를 한번에 보여주거나 무한 스크롤에서 계속 요소들이 쌓일 때, 로그 화면처럼 엄청난 스크롤이 생길 때 유용합니다
원리
- 높이 계산
- 스크롤 위치 계산 및 보여야 할 요소계산
높이 계산
기본적으로 아이템의 높이와 폭을 알고 있어야 하고 존재하는 모든 아이템의 높이와 폭을 알아야 합니다
const totalHeight = itemHeight * itemCount;
스크롤 위치를 감지해 현재 필요한 인덱스만 계산
현재 스크롤을 할 때 보여야 할 아이템을 구해야합니다
const startIndex = floor(스크롤이 내려간 거리 / itemHeight);
const endIndex = startIndex + 화면에 필요한 아이템 수;
빈 공간 주기
아이템이 전부 렌더링 되지 않기 때문에 중간에 위치해야 할 실제 자리로 배치하려면 위 아래에 빈 공간을 주어서 실제로는 다른 요소들이
있는 것처럼 속인다
const offsetTop = 100 * 50 = 5000;
const offsetBottom = 100 * 50 = 5000;
무한 스크롤 vs 가상 스크롤
둘은 이름은 비슷하지만 완전히 다른 기능입니다 하지만 두 기능 같이 유용하게 쓰입니다 두 기능의 차이를 정리 해보았습니다
| 구분 | 무한 스크롤 | 가상 스크롤 |
| 목적 | 데이터 계속 불러오기 | 렌더링 최적화하기 |
| 핵심 동작 | 리스트 끝 도달 시 api 요청 전송 | 스크롤 위치 변화에 따라 렌더 구간 변경 |
| 데이터 크기 | 계속 증가 | 동일, 증가하지 않음 (단지 렌더링만 최적화) |
| 렌더링 수 | 무제한 증가 | 항상 일정한 개수 |
| 메모리 영향 | 시간이 지날수록 증가 | 일정하게 유지 |
내가 적용한 것
저는 scrolloop라는 라이브러리를 사용하여서 가상 스크롤을 적용하였습니다
https://www.npmjs.com/package/scrolloop
이 링크를 타고 가시면 라이브러리에 대해서 더 자세히 알 수 있습니다
저는 이 라이브러리를 이용하여 간단한 구현에 성공 하였습니다 제가 구현한 코드를 보여드리겠습니다
처음에 뚝뚝 끊기거나 마지막에 부자연스럽게 아이템이 사라지는 경우가 있었는데 overscan을 올리니 아무 문제 없이 잘 작동하였습니다
import { RefreshControl } from 'react-native';
import Post from '~/shared/ui/Post';
import { useLocalSearchParams } from 'expo-router';
import { ProductType } from '~/shared/types/type';
import { ModeType } from '~/shared/types/mode';
import { useCallback, useState } from 'react';
import { useGetPosts } from '~/shared/model/useGetPosts';
import { returnValue } from '~/view/post/model/handleCategory';
import { Category } from '~/view/post/model/category';
import { VirtualList } from 'scrolloop/native';
export default function PostList({ category }: { category: Category }) {
const { type } = useLocalSearchParams<{ type: ProductType; mode?: ModeType }>();
const [refreshing, setRefreshing] = useState(false);
const currentMode = category ? returnValue(category) : undefined;
const { data = [], refetch } = useGetPosts(
currentMode as ModeType | undefined,
type as ProductType | undefined
);
const onRefresh = useCallback(async () => {
setRefreshing(true);
try {
await refetch();
} finally {
setRefreshing(false);
}
}, [refetch]);
return (
<VirtualList
decelerationRate={0}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
itemSize={120} // 아이템 사이즈
overscan={12} // 보이는 아이템 갯수 말고 얼마나 더 렌더링 할 것인지
count={data.length} // 아이템 갯수
renderItem={(index) => {
const item = data[index];
if (!item) return null;
return <Post {...item} />;
}}
/>
);
}
글을 마치며
오늘은 제가 제 프로젝트 refactor 성능 개선을 하면서 어떤 걸 도입해야 좋을지 고민하며 공부하고 제가 적용한 것을 보여드렸습니다
이걸 하면서 한 번에 두마리 토끼를 잡은 것 같아서 좋았던거 같습니다 여러분들도 더 좋은 방법을 잘 골라서 사용하시면 좋을거 같습니다
다음에는 꼭 제가 직접 한 번 구현해보는 것도 괜찮을거 같습니다 글을 읽어주셔서 감사합니다

