AJAX용 무한 스크롤 모듈 구현하기

둘러봐 기술블로그·2023년 10월 11일

Toy Project 🪀

목록 보기
4/4

결과물

개발 동기

1. 현재 진행 중인 프로젝트에 필요해서

최근 조그마한 프로젝트를 진행 중이다. 여러 기능을 구현해야 하는데, 그 중 하나가 트위터처럼 스크롤을 통해 댓글을 업데이트하는 기능이라서 구현했다.

2. 돈 주고 이 기능을 사지 말라고

무한 스크롤 기능 구현을 위해 여러 자료를 찾아봤는데, 그 중 몇몇은 돈을 줘야 볼 수 있거나, 코드 자체를 돈으로 판매하는 경우도 있었다. 혹시나 무한 스크롤 기능을 급하게 구현해야 하는 사람이 있다면, 굳이 돈 주고 코드 사지 말고 이 글을 참고하면 좋겠다.

설계

목표 : 다양한 경우에서 쓸 수 있는 모듈 형식 구현

이 글의 목적 중 하나는 급하게 무한 스크롤 기능을 구현해야 하는 사람들이 내 코드를 이용할 수 있도록 하는 것이다. 그런 만큼 여러 상황에 내 코드를 사용할 수 있도록 모듈 형식으로 구현할 것이다.

완성된 addInfiniteScroll 함수는 무한 스크롤과 관련된 객체들의 태그나, 크기와 무관하게 작동하게 될 것이다.

함수 인자 선택

단순하게 생각해보자. AJAX로 무한 스크롤 기능을 구현할 때 중요한 등장인물은 누구 누구가 있을까?

  • target(DOM 객체) : 일단 스크롤이 일어나는 대상이자 AJAX로 받아온 데이터로 내용을 추가할 DOM 객체가 있을 것이다. 이 DOM 객체에 AJAX 코드를 포함한 함수를 scrollend 이벤트 리스너로 추가해야 한다.

  • promise(Promise 객체) : 다음으로 서버에 리소스를 요청하고, 응답되면 리소스를 반환하는 함수가 필요하다. AJAX를 "리소스 요청-응답(1) 후 DOM 객체 생성(2)" 이런 식으로 두 부분으로 나누자면 (1)에 해당하는 부분이다. Promise 객체인 이유도 (1) 부분이 비동기로 이뤄져야 하기 때문이다.

  • generator(function 객체) : AJAX 두 부분 중 (2)에 해당하는 부분이다. 목적에 맞게 리소스를 DOM 객체로 변환하는 함수가 필요하다. 위의 결과물에서는 url을 받아 <img> 객체를 생성하는 함수가 쓰였다. 함수를 어떻게 정의하던 리소스를 받아 DOM 객체로만 반환하면 되기 때문에 이미지가 아니라 댓글 같이 다른 요소에도 적용 가능하다.

    • 이때 generator로 생성되는 DOM 객체에 infinite-scroll-component 라는 클래스를 추가한다. ES6 에 익숙하지 않은 사람들이 CSS 파일에서 .infinite-scroll-component 을 통해 스타일 설정을 할 수 있도록 하기 위함이다.
  • loader(DOM 객체, 선택) : AJAX 과정에서 코드가 작동되고 있다는 걸 보여주기 위해 있으면 좋다. loader는

    이런 걸 말한다. 아마 익숙할 것이다.

구현

함수 정의

function addInfiniteScroll(target, promise, generator, loader){

앞서 언급한 대로 target, promise, generator, loader 순으로 인자를 받는다.

스크롤을 위한 스타일 설정

  target.infiniteScrollFlag = true;
  target.style.display = "flex";
  target.style.flexWrap = "wrap";
  target.style.overflowY = "scroll";

reference로 넘겨 받은 target의 스타일을 변경한다. 특히 overflowY = "scroll" 스타일 설정을 안 하면 스크롤 이벤트 자체가 안 생겨서 반드시 설정해야 한다. 나머지 스타일 설정은 필수는 아니다. target.infiniteScrollFlag은 이벤트 리스너의 작동을 컨트롤하는 flag 변수이다.

scrollend 이벤트 리스너 추가

  target.addEventListener("scrollend", function(){
      if(target.infiniteScrollFlag){
         let loaderCopy;
         if (loader != undefined){
           loaderCopy = loader.cloneNode(true);
           target.appendChild(loaderCopy);
         }
         target.infiniteScrollFlag = false;
         promise.then((resolve) => {
            (loaderCopy != undefined)&&(loaderCopy.remove());
            let newElement = generator(resolve);
            newElement.classList.add("infinite-scroll-component");
            target.appendChild(newElement);
            target.infiniteScrollFlag = true;
         });
      }
    });

넘겨 받은 promisegenerator, 그리고 loader를 통해 적절한 무한 스크롤 함수를 구현한다.

  1. target.infiniteScrollFlag가 true인지 확인한다. (false면 이미 이벤트 리스너가 작동 중이라는 의미이므로 넘어간다.)
  2. loader를 넘겨받았으면 복사한 다음 target에 추가한다.
  3. target.infiniteScrollFlag을 false로 바꾸고, promise를 비동기로 작동시킨다.
  4. Promise:then을 통해 promise가 끝나면, loader 복사본을 삭제하고 generator를 통해 DOM 객체를 생성한 다음 target에 추가한다.
  5. target.infiniteScrollFlag을 true로 바꿔서 다시 이벤트 리스너가 자동할 수 있도록 한다.

예제 (위의 결과물)

HTML

<body style = "text-align:center">
  <h3>AJAX : 0</h3>
  <div id = "result" style = "width : 200px; height : 200px; background-color : gray; margin-left :auto; margin-right:auto;">
    <img id = "test" src = "https://random.imagecdn.app/500/150">
    <img id = "test" src = "https://random.imagecdn.app/500/150">
    <img id = "test" src = "https://random.imagecdn.app/500/150">
    <img id = "test" src = "https://random.imagecdn.app/500/150">
  </div>
</body>

CSS

loader를 위해 필요하다. w3의 css 설정을 변형했다. 근데 정작 응답이 빨리 이뤄져서 loader는 랜더링되기도 전에 삭제되어 예제에서는 보이지 않는다.

 .loader {
      border: 8px solid #f3f3f3;
      border-top: 8px solid #F1ABCC;
      border-radius: 50%;
      width: 40px;
      height: 40px;
      animation: spin 2s linear infinite;
      margin-left: auto;
      margin-right: auto;
    }

    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }

JavaScript

테스트용 객체 생성

let target = document.querySelector("#result");
let promise = fetch("https://random.imagecdn.app/500/150");
let generator = function(response){
  let img = new Image();
  img.src = response['url'];
  return img;
};
let loader = document.createElement("div");
loader.classList.add("loader");

모듈 적용

// 한 줄이면 된다.
addInfiniteScroll(target, promise, generator, loader);

개발 후기

재밌다

예전에 인스타그램으로 무한 스크롤 기능을 처음 봤을 때, 되게 좋은 UX를 가진 기능이라고 생각했다. 그걸 지금 혼자서 구현하니 재밌다.

profile
move out to : https://lobster-tech.com?utm_source=velog

0개의 댓글