데이터를 많이 부를 때 페이지 단위로 관리하는 pagination, 스크롤에 따라 데이터를 부르는 infinity scroll등 다양한 벙법을 사용합니다. 여기서는 IntersectionObserver를 활용해 스크롤에 따라 데이터를 부르는 infinity scroll을 구현 해보겠습니다.
IntersectionObserver(교차 관찰자 API)는 타겟 엘레멘트와 타겟의 부모 혹은 상위 엘레멘트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API이다.
즉 내가 정한 target이 viewport(혹은 특정 element)안에 있는지 여부에 따라 callback함수를 실행해주는 api이다.
option에서 root는 기본값이 viewport이며 특정 element로 설정 할 수 있다.
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
IntersectionObserver를 생성 후 target element를 observe하면 해당 element가 viewport에 있는지의 여부를 비동기로 확인 후 callback함수를 실행한다.
let target = document.querySelector('#listItem');
observer.observe(target);
위의 api를 활용해 영화 리스트의 맨 마지막 부분이 viewport안에 있으면 api를 호출해 데이터를 추가한다.
├── index.html
└── src
├── App.js
├── main.js
├── api
│ └── movieApi.js
├── components
│ └── ScrollView.js //추가
├── css
│ └── style.css //추가
└── util
└── InfinityScroll.js //추가
간단한 스타일 작업을 해줍니다.
body {
height: 100vh;
}
.container {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
flex: 1;
}
.block {
width: 250px;
height: 350px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 3px 0;
margin-left: calc((100% - (20% * 4)) / 4);
border: 1px solid black;
font-size: 10px;
}
.block > img {
width: 200px;
height: 280px;
}
만들어준 css파일을 연결해줍니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>JS STUDY</title>
<link rel="stylesheet" href="./src/css/style.css" />
<script type="module" src="./src/main.js"></script>
</head>
<body>
<div class="app"></div>
</body>
</html>
callback 함수의 첫번째 인자값은 현재 observe중인 target들입니다. 이 target의 isIntersecting
값이 true인 경우 해당 target이 viewport에 있다고 생각하면 됩니다. 이를 활용해서 target이 target.isIntersecting === true
인 경우 즉, viewport에 있는 경우 데이터를 불러옵니다. 또한 callback함수의 두번째 인자값은 생성되는 IntersectionObserver를 뜻합니다. 데이터를 불러올 때 해당 target에 대한 관찰을 unobserve
해줍니다.
const InfinityScroll = (fetchData) => {
return new IntersectionObserver((entires, observer) => {
entires.forEach((entry) => {
if (entry.isIntersecting) {
fetchData();
observer.unobserve(entry.target);
}
});
}, {});
};
export default InfinityScroll;
yts api는 page기능을 제공해줍니다. api호출 시 page값을 넘겨줘서 새로운 데이터를 받아 올 수 있도록 해줍니다.
...
const api = {
getMovies: async (page) => {
try {
const movies = await request(
`https://yts.mx/api/v2/list_movies.json?limit=15&page=${page}`
);
return {
isError: false,
data: movies?.data?.movies,
};
} catch (e) {
return {
isError: true,
data: e,
};
}
},
};
export { api };
이전에 만들어둔 Hello component는 삭제하고 새로운 ScrollView component를 만들어 줍니다. 기본 틀은 Hello component와 비슷하나, 위에서 만든 InfinityScroll.js를 활용해 Scroll에 따라 fetchData함수를 호출해줍니다.
먼저 constructor에서 만들어 둔 InfinityScroll.js의 함수를 통해 IntersectionObserver를 생성해줍니다.
...
constructor({ $target, data, fetchData }) {
this.section = document.createElement("section");
this.section.classList.add("container");
this.data = [];
fetchData();
this.intersection = InfinityScroll(fetchData);
$target.appendChild(this.section);
}
...
이후 setIntersection함수를 만들어 줍니다. 해당 함수는 불러온 데이터 중 가장 마지막 데이터를 가지고 있는 element를 위에서 만들어준 IntersectionObserver에 observe하는 역할을 합니다.
setIntersection() {
const blocks = document.querySelectorAll(".block");
this.intersection.observe(blocks[blocks.length - 1]);
}
ScrollView.js
import InfinityScroll from "../util/InfinityScroll.js";
export default class ScrollView {
constructor({ $target, fetchData }) {
this.section = document.createElement("section");
this.section.classList.add("container");
this.data = [];
fetchData();
this.intersection = InfinityScroll(fetchData);
$target.appendChild(this.section);
}
setData(data) {
this.data = data;
this.render();
}
setIntersection() {
const blocks = document.querySelectorAll(".block");
this.intersection.observe(blocks[blocks.length - 1]);
}
render() {
this.section.innerHTML = "";
this.data.forEach((val, idx) => {
const block = document.createElement("div");
block.classList.add("block");
const title = document.createElement("h1");
title.innerText = val.title;
const image = document.createElement("img");
image.src = val.medium_cover_image;
block.appendChild(title);
block.appendChild(image);
this.section.appendChild(block);
});
this.setIntersection();
}
}
기존에 만들어둔 Hello component대신 ScrollView component를 만들어줍니다. 이때 api에서 데이터를 불러올 때 page값을 넘겨줍니다.
import ScrollView from "./components/ScrollView.js";
import { api } from "./api/movieApi.js";
export default class App {
constructor($target) {
let page = 1;
const scrollView = new ScrollView({
$target,
fetchData: async () => {
const response = await api.getMovies(page);
page += 1;
if (!response.isError) {
const newData = [...scrollView.data, ...response.data];
scrollView.setData(newData);
} else {
//에러처리하기
}
},
});
}
}
아래 명령어로 실행 후 http://localhost:8080 에 가서 결과물을 확인해봅니다.
npx http-server ./
스크롤에 따라 데이터가 잘 불러와 지는지 확인해 봅니다.
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API
https://github.com/woohyeonjo/ilovecat-javascript
https://yts.mx/api