본문 바로가기
JavaScript

IntersectionObserver API로 무한 스크롤 만들기

by Vintz 2021. 10. 8.
반응형

무한 스크롤(Infinite scroll)

IntersectionObserver APITOC 만들기 글에 이어서 이번엔 무한 스크롤을 만들어 봤다. 이젠 구현만이 목적이 아닌 코드 스타일이나 프로그래밍 방식에 대해서도 신경을 쓰고 있다. '그래도 성장은 하고 있구나 🌱'라고 생각한다.

개발 전용차선 강의 사이트

다음과 같은 상황이라고 생각하고 구현을 했다.

 

내가 훗날 남을 가르칠 정도의 실력이 된다 느껴져서 나만의 개발 강의 사이트를 만들려고 한다. 편하게 강의 목록을 볼 수 있도록 무한 스크롤을 구현한다.

  1. 서버에서 받아온 응답 결과를 노출 시키기(강의 리스트 렌더링)
  2. 로딩하는 동안 로딩 중인 것을 표시하기
  3. 스크롤 바닥을 위치하면 새로운 강의 노출

더미 데이터 만들기

나는 일단 서버를 구현할 실력이 아직 안되기 때문에 더미 데이터를 만들어 강의를 노출시키는 것으로 만족해야 했다.

const getRandomImage = () => {
  // 300보다 크거나 같고, 1200보다 작은 길이
  const width = Math.floor(Math.random() * (12 - 3) + 3) * 100;
  // 300보다 크거나 같고, 900보다 작은 높이
  const height = Math.floor(Math.random() * (9 - 3) + 3) * 100;
  return `https://via.placeholder.com/${width}x${height}`;
};

let id = 0;
const courses = (cnt) => {
  const result = Array.from({ length: cnt || 20 }, (undefined, i) => ({
    id: i + 1 + id,
    title: `Lorem ipsum dolor sit amet ${i + 1 + id}`,
    imageURL: getRandomImage(),
  }));
  id += cnt || 20;
  return result;
};

export default courses;

길이가 100인 배열을 생성하고, 길이만큼 강의에 대한 데이터 객체를 넣어주었다. id 부분을 어떻게 적용시켜야할지 고민을 했었다. courses()를 실행할 때마다 id를 증가시켜야 하기 때문에 위와 같이 만들게 되었다. cnt는 내가 원하는 개수만큼 데이터를 생성할 수 있게 하고, 기본값은 20으로 설정했다.

 

이미지는 유튜브 API처럼 이미지의 크기가 고정으로 정해져 있을 수도 있지만 이번 경우엔 다르게 적용시켜 봤다. 참고로 placeholder.com은 다양한 크기의 placeholder 이미지들을 무료로 제공해주는 서비스이다.

위와 같이 다양한 크기의 이미지를 갖는 강의들이 생성되었다.

만들어진 데이터 노출 하기

이제 데이터는 만들어졌으니 만든 데이터를 노출 시켜보자.

// 📂 index.html
// <main>
//   <ul id="courseList" class="course-list"></ul>
//   <div id="observer"></div>
// </main>

// 📂 main.js
const courseList = document.querySelector('#courseList');

const showCourses = () => {
  const cnt = 15;
  const course = courses(cnt).map((course) => createCourse(course));
  return courseList.append(...course);
};

const createCourse = (course) => {
  const li = document.createElement('li');
  li.setAttribute('class', 'course-item');
  li.insertAdjacentHTML(
    'beforeend',
    `
      <div class="thumbnail-container">
        <img class="course-thumbnail" src="${course.imageURL}" alt="개발 전용차선 강의">
      </div>
      <h1 class="course-title">${course.title}</h1>
    `
  );
  return li;
};

먼저 강의 리스트를 노출 시키는 showCourses 함수를 만들자. cnt15로 하고, 만들어진 데이터 개수만큼 map()을 통해 새로운 강의들을 생성한다. 그 후 append()로 생성한 강의들을 노출 시켜준다. showCourses()를 실행하면 15개의 강의가 렌더링 될 것이다.

 

li.innerHTML이 아닌 li.insertAdjacentHTML()을 사용한 이유는 좀 더 유연하기 때문이다. 만약 li 요소에 다른 요소가 존재할 경우, innerHTML은 기존 존재하던 요소까지 건들며 해당 요소내 전체 값을 바꾼다. 하지만 insertAdjacentHTML()은 기존 요소는 건들지 않고, 새로운 값의 위치까지 정할 수 있다. 해당 프로젝트에선 크게 상관이 없지만 좀 더 직관적이기도 하고 유지보수가 좀 더 쉬울 것 같아 적용하게 됐다.

무한 스크롤 구현하기

let page = 0;
const showMore = async () => {
  const target = page ? observer : courseList;
  target.classList.add('loading');
  await delayTime(showCourses);
  page++;
  target.classList.remove('loading');
};

const io = new IntersectionObserver(([{ isIntersecting }]) => {
  if (isIntersecting) showMore();
});

io.observe(observer);

const randomTimer = (func) => (resolve) => {
  const time = Math.floor(Math.random() * 5) * 1000;
  setTimeout(() => resolve(func()), time);
};

const delayTime = (func) => new Promise(randomTimer(func));

최초 강의 노출 시 로딩화면과 스크롤 할 때의 로딩화면을 다르게 하고 싶었다. 또한 구조분해 할당을 통해 IntersectionObserver의 콜백 entries를 원하는 것만 뽑아와 가독성을 좋게하고 코드량을 훨씬 많이 줄일 수 있었다.

 

delayTime()의 경우 유튜브 영상을 참고하여 구현했는데, 이렇게도 조합이 가능하단걸 알게 되었고 다양하게 활용이 가능할 것 같았다.

마무리

해당 프로젝트를 만들면서 흥미로운 코드를 만들 수 있었고 재미있게 프로그래밍을 했다. 만들면서 중간중간 블로그 글로 쓸 내용들을 생각하고 부푼 기대를 안고 작성했는데 막상 코드 설명을 하려니 역시 어려웠다. 그래도 끝까지 마무리 짓게 되어 기쁘다. 😁

 

전체 코드보기: https://github.com/ByungyeonKim/Infinite-scroll

반응형