무한 스크롤(infinite scroll)은 웹 페이지에서 사용자가 스크롤을 내리면 자동으로 콘텐츠를 로드하여 페이지의 끝이 없는 것처럼 보이게 하는 기능이다. 이 기능을 Vanilla JavaScript로 구현할 예정이다. 무한 스크롤은 스크롤 이벤트로 구현할 수도 있고, Intersection Observer API로 구현할 수도 있지만 이번 글은 스크롤 이벤트로 구현해볼 것이다.
데이터를 비동기식으로 가져와서 처리할 것이다. 무한 스크롤 구현을 위해 백엔드 환경을 별도로 구축한다는건 굉장히 비효율적이고, 배열이나 객체로 임시 데이터를 만드는 것도 마찬가지다.
그러므로 JSONPlaceholder를 이용하여 임시 JSON 데이터를 제공해주는 서비스를 이용할 것이다. 사용법도 매우 간단하여 그냥 호출만 해주면 된다.
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((json) => console.log(json));
👇 Output
{
id: 1,
title: '...',
body: '...',
userId: 1
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>자바스크립트 무한스크롤</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="script.js"></script>
</html>
추후에 자바스크립트로 데이터를 불러와서 div 요소에 접근한다음, HTML을 추가해줄것이다.
let currentPage = 1;
let isFetching = false;
let hasMore = true;
let root = document.getElementById('root');
async function fetchData() {
isFetching = true;
let response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${currentPage}`);
let data = await response.json();
console.log(data);
isFetching = false;
if (data.length === 0) {
hasMore = false;
return
}
for(let post of data) {
let div = document.createElement('div');
div.innerHTML = `<h2>${post.title}</h2><p>${post.body}</p>`
root.appendChild(div);
}
currentPage++;
}
fetchData()
let currentPage = 1
현재 로드된 페이지의 번호를 나타내는 변수. 초기값은 1로 설정.
let isFetching = false
데이터를 가져오는 중인지 여부를 나타내는 변수. 초기값은 false로 설정.
let hasMore = true
가져올 더 많은 데이터가 있는지 여부를 나타내는 변수. 초기값은 true로 설정.
let root = document.getElementById('root')
페이지에 콘텐츠를 추가할 요소를 나타내는 변수. 'root'라는 id를 가진 요소에 접근하여 변수에 할당.
async function fetchData() { ... }
fetch 함수를 사용하여 데이터를 비동기식으로 가져온다. 데이터를 가져오는 중이기 때문에 isFetching = true;
로 변경해주고, 응답을 받았으면 다시 isFetching = false
로 변경해준다.
스크롤 할 때마다 페이지 번호를 변경하는 방식으로, 페이지 번호가 변경되면 그 다음 10개의 레코드가 로드된다.
https://jsonplaceholder.typicode.com/posts?_page=1
https://jsonplaceholder.typicode.com/posts?_page=2
데이터의 길이가 0이면 더 이상 데이터를 가져올 필요가 없기 때문에 hasMore
변수를 false로 설정하고 함수를 끝낸다.
for(let post of data) { ... }
가져온 데이터를 반복문 돌려서 각 데이터에 대해 HTML 요소를 생성하고 root 요소에 추가한다. 그 이후 현재 페이지(currentPage)를 1 증가시킨다.
스크롤을 아래로 내리면 데이터가 추가로 생성되지는 않는다. 아직까지는 페이지를 맨 아래로 스크롤하면 추가 데이터를 요청하지 않기 때문이다.
window.addEventListener('scroll', () => {
if (isFetching || !hasMore) {
return
}
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
fetchData();
}
})
window.addEventListener('scroll', () => { ... })
window 객체에 스크롤 이벤트를 등록한다. 스크롤이 발생할 때마다 이벤트 리스너가 실행된다.
window 객체는 모든 객체가 소속된 객체이고, 전역객체이면서, 창이나 프레임을 의미한다.
if (isFetching || !hasMore) { return }
isFetching
이 true인 경우에는 추가적인 데이터 요청을 방지하기 위해 return 문으로 함수 실행을 중단한다.hasMore
은 더 가져올 데이터가 있는지 여부를 나타내는 변수다. 만약 가져올 데이터가 더 이상 없다면, 즉 hasMore이 false인 경우에도 추가적인 데이터 요청을 방지하기 위해 return 문으로 함수 실행을 중단한다.(window.innerHeight + window.scrollY) >= document.body.offsetHeight
현재 스크롤 위치(window.scrollY)와 브라우저 창의 높이(window.innerHeight)를 합친 값이 문서의 전체 높이(document.body.offsetHeight)와 같거나 큰 경우에만 아래 코드를 실행한다. 즉, 사용자가 페이지의 맨 아래에 도달했을 때를 의미.
let currentPage = 1;
let isFetching = false;
let hasMore = true;
let root = document.getElementById('root');
async function fetchData() {
isFetching = true;
let response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${currentPage}`);
let data = await response.json();
console.log(data);
isFetching = false;
if (data.length === 0) {
hasMore = false;
return
}
for (let post of data) {
let div = document.createElement('div');
div.innerHTML = `<h2>${post.title}</h2><p>${post.body}</p>`
root.appendChild(div);
}
currentPage++;
}
window.addEventListener('scroll', () => {
if (isFetching || !hasMore) {
return
}
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
fetchData();
}
})
fetchData();