DOM 복습 + infinite scroll (feat.Vanilla JS)

📅 TIL #143




🎯 Achievement Goals

  1. DOM API 복습
  2. infinite scroll 기능 구현







Intro.


평화로운 주말 오전부터 Vanilla JS 복습을 시작했다!! 기본기를 더 다지기 위해 fetch로 데이터를 받아오고, 이 데이터를 통해 DOM 조작하는 법을 복습하고 싶었다.


그리고 infinite scroll 기능을 React로는 해봤지만 JS로는 해본 경험이 없어서 Vanilla JS로 구현을 해보면서 다시 intersectionObserver에 대해 복습할 수 있어서 좋았다.


그래서 기능들을 다 구현하고 기록을 하고 싶어서 블로깅까지 하기로 했다!
(블로깅하면서 코드 다시 점검하기..)




DOM 복습!


먼저 나는 무료로 데이터를 요청하고 사용할 수 있는 사이트(jsonplaceholder.typicode.com)를 통해 이미지와 제목, 아이디등이 있는 데이터를 받아오고, 이 데이터를 화면에 뿌려 줄 예정이다. 수도 코드를 간략히 적어보고 시작했다.


수도 코드 (pseudo code)
  1. 응답받은 데이터를 외부 변수에 저장한다.( 외부 변수에 저장하는 이유는 나중에 데이터를 추가하는 기능도 만들기 위함이다.)
  2. 각 필요한 태그들을 createElement를 통해 만든다.
  3. 각 태그들에 classList.add를 통해 Class를 추가해준다.
  4. 받아온 데이터를 각 태그들에게 textContent를 통해서 값을 할당한다.
  5. appendChild를 통해 container div에 자식 노드를 추가해준다.
  6. 위의 코드들을 하나의 함수로 묶고, 불러온 데이터에서 10개씩 화면에 보여준다.
  7. 맨 밑에 div 태그를 놓고, div 태그가 observer를 통해 관찰 대상이 되면 데이터를 10개씩 추가로 불러온다. (infinite scroll)


// 데이터 구조
console.log(DATABASE);
[
  {
    "albumId": 1,
    "id": 1,
    "title": "accusamus beatae ad facilis cum similique qui sunt",
    "url": "https://via.placeholder.com/600/92c952",
    "thumbnailUrl": "https://via.placeholder.com/150/92c952"
  },
  {
    "albumId": 1,
    "id": 2,
    "title": "reprehenderit est deserunt velit ipsam",
    "url": "https://via.placeholder.com/600/771796",
    "thumbnailUrl": "https://via.placeholder.com/150/771796"
  },
  ...
]


// index.html
<body>
  <div id="post-list"></div>
</body>


// script.js
const DATABASE = [];

// TODO: 데이터 외부 변수에 저장하기
async function getData() {
  const res = await fetch('https://jsonplaceholder.typicode.com/photos');
  const data = await res.json();
  DATABASE.push(...data);
}

// TODO: infinite scroll를 위해 외부 변수에 저장
let start = 0;
let end = 10;

// TODO: 10개씩 post 불러오기
function postRequest(data, start, end) {
  for (let i = start; i < end; i++) {
    const { title, thumbnailUrl, id } = data[i];
    makePost(title, thumbnailUrl, id);
  }
};

// TODO: document에 한 번만 접근한다.
let doc = document;
const postLists = doc.getElementById('post-list');

// TODO: add post tag to post list
function makePost(postTitle, postThumbnailUrl, postId) {
  const wrap = doc.createElement('div');
  wrap.classList.add('post-wrap');

  const thumbnailUrl = doc.createElement('img');
  thumbnailUrl.classList.add('post-thumbnailUrl');
  thumbnailUrl.src = postThumbnailUrl;

  const title = doc.createElement('div');
  title.classList.add('post-title');
  title.textContent = postTitle;

  const id = doc.createElement('div');
  id.classList.add('post-id');
  id.textContent = postId;

  wrap.append(thumbnailUrl, title, id);
  postLists.appendChild(wrap);
};

function render() {
  getData()
  
  setTimeout(() => {
    postRequest(DATABASE, start, end);
  }, 500);
}

render();


dom


먼저 DOM을 조작해서 화면에 데이터들을 보여줄 수 있게 구현을 해 보았다. 한 가지 특이점은 render함수에서 setTimeout을 사용해서 데이터를 뿌려주었다. 처음에는 그냥 뿌려주었었는데, 오류가 났었다. 조금만 생각을 해보니, 데이터를 요청하고 받아오는데 시간이 필요했다. 이 부분은 이벤트 루프에 대한 이해도가 있으면 금방 생각이 날 수 있었던 문제였다.


다음으로는 이제 list가 끝나는 지점에 div 태그를 하나 더 추가해놓고, intersectionObserver API를 통해 무한 스크롤 기능을 구현을 하면 된다.


맨 밑에 추가한 div 태그가 관찰 대상이 될 때마다, 데이터를 10개씩 추가로 불러오면 된다. 이 코드들은 render 함수안에 작성을 해보았다.


// index.html

<body>
  <div id="post-list"></div>
  <div class="list-end"></div>
</body>


// script.js

// ... 중간 생략 ... //

function render() {
  getData();
  const listEnd = doc.querySelector('.list-end');
  
  setTimeout(() => {
    postRequest(DATABASE, start, end);
  }, 500);
  
  const observerCallback = (entries) => {
    entries.forEach(entry => {
      const { target } = entry;

      // TODO: 해당 요소가 교차되었을 때
      if (entry.isIntersecting) {
        if (end <= DATABASE.length 
        && target.getBoundingClientRect().y > 362) {
          start = end;
          end += 10;

          // start, end 변수 재할당
          // 그리고 10개를 추가로 불러온다.
          postRequest(DATABASE, start, end);

          observer.unobserve(target);
        }
      } else {
        return;
      }

      observer.observe(target);
    });
  };

  const observerOptions = {
    root: null,
    rootMargin: "0px",
    threshold: 0.3,
  }

  const observer = new IntersectionObserver(
    observerCallback,
    observerOptions,
  );

  // list-end를 관찰 대상으로 등록한다. 
  observer.observe(listEnd);
};

render();


infinite_scroll


intersectionObserve API를 잘 활용하면 손쉽게(?) 무한 스크롤 기능을 구현할 수 있다. 저 데이터가 총 5000개가 있는데, 데이터를 한 번에 렌더링하지 않고 10개씩 불러오기 때문에 성능은 물론 유리하고, 활용도가 높은 기술이다. React로 구현했을 때와 큰 차이는 없지만, JS로 구현을 해보니 또 다른 느낌이 들었다. 어렵지 않으면서, 활용도가 많은 기술이라 자주 사용해 보는 것이 중요한 것 같다.







마무리.

오늘 Vanilla JS로 복습한 내용은 여기까지 블로깅을 하고, 다음에 추가적으로 기능들을 구현하고 다시 블로깅을 해야겠다! 차근 차근 React의 의존도를 낮추면서 아주 근본인 Javacript를 공부하다보니, 기본기가 쌓여가는 기분이 든다~~ 아주 좋은 주말을 보낸 것 같다~!!


다음에는 이 코드에 이어서 post 추가 기능과 post 필터링 기능을 구현하고 블로깅할 예정이다.




데이터 무료 사이트
Git Repositories