게시판의 많은 양의 글을 편리하게 보기 위해 무한 스크롤 방법으로 페이징 기능을 구현해 봤습니다.
무한 스크롤은 사용자가 페이지 하단에 도달했을 때, 콘텐츠가 계속 로드되는 사용자 경험(UX) 방식입니다. 한 페이지 아래로 스크롤 하면 끝없이 새로운 화면을 보여주게 되고 이로 인해 많은 양의 콘텐츠를 스크롤 해서 볼 수 있습니다.
사용자 참여 및 콘텐츠 탐색이 쉽습니다.
무한 스크롤이 클릭하는 것보다 더 나은 사용자 경험을 제공합니다.
→ 다음 콘텐츠를 보기 위한 추가 클릭이 필요없고 페이지 로드 시간이 짧습니다.
터치스크린(모바일)일때 더 유용하게 적용됩니다.
→ 화면이 작을수록 스크롤은 길어지기에 모바일 환경에서 콘텐츠를 보여주기 직관적이고 사용하기 쉬운 형식 입니다.
다양한 페이징 기능들 중 제가 무한 스크롤 기능을 택한 이유는 일단, 사용자의 클릭을 최소화 하면서 한 번에 많은 양의 데이터를 보여주고 싶다는 생각을 했고 페이지 네이션 기능을 사용했을 때 보다 무한 스크롤을 사용하면 사용자들이 더 쉽게 다양한 콘텐츠를 볼 수 있기 때문에 무한 스크롤 기능을 택하게 되었습니다.
제가 생각한 무한 스크롤을 구현했을 때 가져야할 기능들입니다. 하나씩 차근차근 구현해보도록 하겠습니다.
JS로 무한스크롤을 구현하는 방법은 다양합니다. 그 중 저는 IntersectionObserver API
를 이용했습니다.
🤔IntersectionObserver란?
IntersectionObserver
인터페이스는 대상 요소와 그 상위 요소 혹은 최상위 도큐먼트인 viewport
와의 교차 영역에 대한 변화를 비동기적으로 감지할 수 있도록 도와줍니다.
즉, arget Element
가 화면에 노출되었는지 여부를 간단하게 구독할 수 있는 API
입니다.
var intersectionObserver = new IntersectionObserver(function(entries) {
// If intersectionRatio is 0, the target is out of view
// and we do not need to do anything.
if (entries[0].intersectionRatio <= 0) return;
loadItems(10);
console.log('Loaded new items');
});
// start observing
intersectionObserver.observe(document.querySelector('.scrollerFooter'));
IntersectionObserver
는 Callback
함수를 통해 두개의 매개변수를 받습니다.
new IntersectionObserver(callback[, options]);
entries
→더 보이거나 덜 보이게 되면서 통과한 역치를 나타내는, IntersectionObserverEntry (en-US)
객체의 배열.
observer
→ 자신을 호출한 IntersectionObserver.
이 기능을 이용해 화면 맨 아래에 div
요소를 넣어 이 div
요소를 IntersectionObserver
가 감시하도록 구현했습니다.
<div class="list"></div>
<p id="sentinel"></p>
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
//entry가 interscting 중이 아니라면 함수를 실행하지 않습니다.
if (page._scrollchk) return;
//현재 page가 불러오는 중임을 나타내는 flag를 통해 불러오는 중이면 함수를 실행하지 않습니다.
observer.observe(document.getElementById('sentinel'));
//observer를 등록합니다.
page._page += 1;
//불러올 페이지를 추가합니다.
page.list.search();
//페이지를 불러오는 함수를 호출합니다.
});
});
io.observe(document.getElementById('sentinel'));
IntersectionObserver
를 통해 실행된 search
함수에서 ajax
를 통해 아이템을 가져옵니다.
이때, beforeSend
와 complete
를 통해서 Skeleton UI와 Loading animation 삽입, 삭제를 구현했습니다.
$.ajax({
url: url,
data: param,
method: "GET",
dataType: "json",
success: function (result) {
console.log(result);
},
error: function (err) {
console.log(err);
},
beforeSend: function () {
_scrollchk = true;
//데이터가 로드 중임을 나타내는 flag입니다.
document.getElementById('list').appendChild(skeleton.show());
//skeleton을 그리는 함수를 이용해 DOM에 추가해줍니다.
$(".loading").show();
//loading animation을 가진 요소를 보여줍니다.
},
complete: function () {
_scrollchk = false;
//데이터가 로드 중임을 나타내는 flag입니다.
$(".loading").hide();
skeleton.hide();
//loading animation 요소와 skeleton을 지우는 함수를 이용해 DOM에서 지워줍니다.
}
});
모든 아이템이 다 불러오거나 검색 시 20개의 아이템보다 적게 검색되는 경우 더 이상 IntersectionObserver
의 감시를 받지 않도록 만들어야 합니다.
IntersectionObserver.unobserve(target);
IntersectionObserver API
에는 지정된 대상 요소 관찰을 중지하는 unobserve
메서드가 존재하는데 이를 이용해 관찰을 중지할 수 있습니다.
var observer = new IntersectionObserver(callback);
observer.observe(document.getElementById("elementToObserve"));
/* ... */
observer.unobserve(document.getElementById("elementToObserve"));
이렇게 관찰을 중지할 수도 있지만, 저는 간단하게 지금 관찰중인 요소를 이용해 관찰 대상을 DOM
에서 숨기는 방식으로 구현했습니다.
if (_total === 0) {
$('#sentinel').hide();
//검색된 아이템이 없을 경우 관찰중인 요소를 숨긴다.
}
else {
if (_total <= _page*20){
$('#sentinel').hide();
//검색된 아이템이 20개 이하일 경우 관찰중인 요소를 숨긴다.
}
else {
$('#sentinel').show();
//관찰중인 요소를 보여준다.
}
}
사용자가 아이템의 상세보기 페이지로 이동 한 후 다시 뒤로가기를 이용해 무한 스크롤 페이지로 돌아왔을 때, 페이지가 다시 맨 처음처럼 로드되어 있다면 다시 보고 있던 아이템까지 스크롤을 해야 하는 불편함이 발생합니다.
이를 해결하기 위해, 기존에 보고 있던 페이지로 스크롤을 옮기는 기능을 추가했습니다.
브라우저 뒤로가기 이벤트 감지를 이벤트 처리해줍니다.
window.addEventListener('pageshow', function (event) {
if (event.persisted || window.performance && window.performance.navigation.type == 2) {
//
}
});
상세보기 페이지로 넘어가기 전 페이지를 sessionStorage
에 저장한 후 뒤로가기 이벤트가 발생하면 sessionStorage
에서 값을 가져와 넘겨줍니다.
if (sessionStorage.getItem("page")) {
var pageNum = Number.parseInt(sessionStorage.getItem("page"));
_page = pageNum ;
list.search();
}
이렇게 완성된 리스트 무한 스크롤입니다.
혹시 소스코드를 받을 수 있을까요?....ㅠㅠ 제가 딱 원하는 기능들을 정말 잘 구현해주셔서 포스팅 읽으면서 따라해보는데 막혀서요 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ