무한 스크롤
컨텐츠를 페이징하는 기법 중 하나로, 스크롤하다 컨텐츠의 마지막 요소를 볼 즈음 다음 컨텐츠가 있으면 불러오는 방식이다.
구현 방식 두 가지
- window의 scroll 이벤트를 통해 스크롤이 일어날 때마다 화면 전체의 height와 스크롤 위치를 통해 스크롤이 컨텐츠의 끝에 도달했는지 체크해서 처리하는 방식
- intersection observer 방식 (지정한 DOM 객체가 보고 있는 시야에 닿았는지 체크)
실습
컴포넌트 구조
- App - 데이터 불러오고 PhotoList 컴포넌트 생성, 불러온 데이터를 pothoList라는 이름으로 내려줌
- PhotoList - 스크롤이 끝에 도달하면 onScrollEnded 콜백을 호출하는 식으로 App 컴포넌트 쪽에 제어권을 넘김
Scroll Event
- 무한 스크롤 구현 전 더보기 버튼 UI를 구현해보는 상황
더보기 버튼을 여러번 클릭하면 다음과 같이 api가 여러번 호출이 되는 문제 상황이 발생한다.
isLoading
state를 추가하여 로딩 중에는 api 호출이 한 번만 되도록 설정하였다.
window.addEventListener("scroll", () => {
const { isLoading, totalCount, photos } = this.state;
const isScrollEnded =
window.innerHeight + window.scrollY >= document.body.offsetHeight;
if (isScrollEnded && !isLoading && photos.length < totalCount) {
onScrollEnded();
}
});
위 코드로 스크롤이 화면의 끝에 닿았는지를 체크할 수 있는데 콘솔에 찍어보면 다음과 같다.
window.addEventListener("scroll", () => {
console.log(
window.innerHeight + window.scrollY >= document.body.offsetHeight
);
});
}
화면 바닥에 닿았을 경우에만 값이 true
가 된다.
document.body.offsetHeight
: 전체 렌더링 된 높이 (body의 높이)window.innerHeight
: 네비게이션 창(탭, url, 북마크)을 제외한 높이window.scrollY
: 현재 스크롤바 위치
따라서 window.innerHeight
와 window.scrollY
을 더하면 전체 컨텐츠에서 스크롤이 얼마나 되었는지 볼 수 있다.
이 경우에는 무조건 스크롤의 끝에 닿은 경우에만 true
가 되기 때문에 100px정도 보정을 하고 싶다면 다음과 같이 작성하면 된다.
window.addEventListener("scroll", () => {
console.log(
window.innerHeight + window.scrollY + 100 >= document.body.offsetHeight
);
});
}
무한 스크롤에도 isLoading
을 체크해야 api 호출이 여러번 되지 않는다.
window.addEventListener("scroll", () => {
const isScrollEnded =
window.innerHeight + window.scrollY + 100 >= document.body.offsetHeight;
if ((isScrollEnded && !this.state.isLoading)) {
onScrollEnded();
}
});
아니면 isLoading
대신에 쓰로틀링(Throttling)을 사용할 수도 있다.
쓰로틀링은 처음 들어온 이벤트만 실행하고 일정 시간동안 들어온 동일한 이벤트는 전부 무시하는 것이다.
현재 페이지가 끝났는데도 스크롤의 끝에 도달할 때마다 계속 호출하고 있다는 문제점이 있다.
마지막 데이터까지 불러왔다는 것을 체크하기 위해서는 컨텐츠의 전체 개수를 불러오는 api 필요하다.
위에서 불러온 값과 현재 내가 불러온 데이터의 개수를 비교해서 체크할 수 있다.
→ totalCount
state 추가
window.addEventListener("scroll", () => {
const { isLoading, totalCount, photos } = this.state;
const isScrollEnded =
window.innerHeight + window.scrollY >= document.body.offsetHeight;
if (isScrollEnded && !isLoading && photos.length < totalCount) {
onScrollEnded();
}
});
}
지금 스크롤 이벤트가 계속 반복되고 있는데 이런 현상은 디바운싱으로 방지할 수 있다.
Intersection Observer
Intersection Observer을 이용해서 가장 마지막에 있는 li가 있을 때를 감지하고 그 후 다음 페이지를 불러올 수 있다.
참고로 Intersection Observer는 무한 스크롤 외에도 이미지 지연에 좋다.
이미지 지연이란 실제로 화면에 보이는 이미지만 그때 바로 불러오는 기법이다.
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !this.state.isLoading) {
if (this.state.photos.length < this.state.totalCount) {
onScrollEnded();
}
}
});
},
{
threshold: 0.5,
}
);
entries
는 observer할 대상을 여러개 정할 수 있다.
속성 중 threshold
는 요소가 얼마만큼 보였는지를 의미한다.
만일 threshold를 1이라고 한다면 요소가 뷰포트에 완전히 들어올 경우에만 감지가 된다.
스크롤 이벤트처럼 매번 이벤트가 발생하지 않기 때문에 선능면에서 더 좋다.
정리
무한 스크롤 UI를 구현하는 방법 두 가지
- scroll 이벤트를 이용해 계산
- Intersection Observer
- observe, unobserve를 잘 해야함
- threshold값으로 observe 대상이 얼마나 노출되어있는지에 따라 동작하게 할 수 있음
무한 스크롤의 단점은 푸터를 보기가 어렵다는 점이다.
따라서 상황에 따라 무한 스크롤 UI보다는 직접 불러오는 인터렉션을 통해 로딩하는 게 더 나을 수도 있다.
출처: 프로그래머스 프론트엔드 데브코스
[Day 25] VanillaJS를 통한 자바스크립트 기본 역량 강화 II (2)
'데브코스' 카테고리의 다른 글
[Day 27] 고양이 사진 검색기 만들기 (0) | 2022.11.22 |
---|---|
[Day 26] 고양이 사진첩 만들기 (0) | 2022.11.22 |
[Day 24] 프로젝트 배포 (0) | 2022.11.17 |
[Day 16] 디바운싱, 커스텀 이벤트 (0) | 2022.11.09 |
[Day 15] Todo App, 이벤트 위임, 낙관적 업데이트 (0) | 2022.11.07 |