pebblepark
개발계발
pebblepark
전체 방문자
오늘
어제
  • 분류 전체보기 (24)
    • Frontend (7)
    • Backend (7)
    • 인프라 (1)
    • CS (0)
      • Design Pattern (0)
    • 정리용 (9)
    • 회고 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • react
  • Docker
  • react-query
  • Context API
  • spring
  • useLayoutEffect
  • CORS
  • @ModelAttribute
  • 스프링 컨테이너
  • 호이스팅
  • Git
  • 스프링
  • debounce
  • 무한스크롤
  • ERR_UNSAFE_PORT
  • TDZ
  • springboot
  • 리액트쿼리
  • hoisting
  • SpringMVC
  • typescript
  • React Query
  • vite
  • github
  • javascript
  • 스프링 빈
  • Github Pages
  • redux
  • 스프링 의존관계
  • wsl

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
pebblepark

개발계발

Frontend

Intersection Observer API를 활용한 무한스크롤 구현

2022. 8. 17. 13:06

Intersection Observer API란

Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다.
- MDN

화면 상의 Target Element 노출 여부를 감지하는 API 이다.

 

스크롤 이벤트 감지 방법과의 비교

  • scroll event를 감지하여 구현한다. 이때 throttle 혹은 debounce 과 같은 처리가 추가로 필요하며 throttle/debounce 를 사용하는 경우 쓰레드 메모리를 차지하고 성능에도 좋지않다.
  • scrollTop + window.clientHeight 등을 사용해서 끝에 도달했는지 계산한다. 이때clientHeight이나 offsetTop 같은 엘리먼트의 위치를 가져와서 계산해야 하는 값은 브라우저가 정확한 값을 구하기 위해 렌더링 큐에 쌓인 모든 작업을 수행하면서 Reflow 를 발생시킬 수 있다.

 

Intersection Observer Syntax

new IntersectionObserver (callback[, options]);

callback 함수는 다음과 같이 2개의 매개변수를 받는다.

  • entries
    👉 더 드러나거나 가려지면서 지정한 역치를 넘어가게 된 요소를 나타내는 IntersectionObserverEntry 객체의 배열
  • observer
    👉 자신을 호출한 IntersectionObserver

자세한 설명은 mdn 참고

 

IntersectionObserver() - Web API | MDN

IntersectionObserver() 생성자는 새로운 IntersectionObserver 객체를 생성하고 반환합니다.

developer.mozilla.org

 

무한 스크롤 구현

간단하게 구현하고자 하는 것은 아래와 같다.

- 리스트 목록 보여주기
- 스크롤 시 리스트 끝에 도달하면 새로운 리스트 추가

 

먼저, 리스트 목록을 보여줄 엘리먼트(ul)와 Intersection Observer의 target 엘리먼트(div)를 추가했다.

<ul id="list"></ul>
<div id="observer"></div>

다음으로, 리스트 목록을 추가하는 함수를 구현했다. 한 번 호출시마다 li 를 10개씩 추가한다.

let count = 0;
const $ul = document.getElementById('list');
function makeListElement() {
  Array(10)
    .fill(0)
    .forEach(() => {
      const $li = document.createElement('li');
      $li.textContent = ++count;
      $ul.appendChild($li);
    });
}

마지막으로, 무한스크롤을 구현하는 부분이다. Intersection Observer의 타겟 엘리먼트가 화면에 보여질 때(isIntersecting) 리스트의 목록을 추가했다. 이때 API 호출시 생기는 딜레마를 표현하기 위해서 1초의 timeout을 지정했다.

let timer;
const $observer = document.getElementById('observer');
const io = new IntersectionObserver((entries) => {
  clearTimeout(timer);
  if (entries[0].isIntersecting) {
    timer = setTimeout(() => makeListElement(), 1000);
  }
});
io.observe($observer);    // observer에 target element 등록

즉, 무한스크롤이 일어나는 과정은 다음과 같다.

  1. ul 태그 밑에 있는 target Element가 화면에 노출된다. (= 리스트의 끝에 도달했다)
  2. 1초의 딜레마 후 리스트에 10개의 엘리먼트가 추가된다.
  3. ul 태그에 자식 엘리먼트가 추가되면서 화면에는 새로 추가된 자식 엘리먼트가 보여진다. target Element 부분은 리스트의 맨 밑으로 이동하여 화면에 보여지지 않게 된다.
  4. 다시 스크롤을 통해 맨 밑으로 내리면(=리스트에 끝에 도달하면) target Element가 화면에 노출되면서 리스트를 추가한다.

 

전체코드

전체코드는 다음과 같다. 실행결과는 Demo에서 확인해볼 수 있다.

<!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>Infinite Scroll</title>
    <style>
      ul {
        list-style-type: none;
        display: flex;
        flex-direction: column;
        align-items: center;
        padding: 0;
      }
      li {
        background: #fff3bf;
        border-radius: 2px;
        margin: 1rem;
        padding: 20% 0;
        text-align: center;
        font-size: 100px;
        width: 80%;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
      }
      li:nth-child(2n) {
        background-color: #c3fae8;
      }
      li:hover {
        box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25),
          0 10px 10px rgba(0, 0, 0, 0.22);
      }
      #observer {
        width: 50px;
        height: 50px;
        margin: 30px auto;
        border: 5px solid rgba(0, 0, 0, 0.12);
        border-radius: 50%;
        border-top-color: #c3fae8;
        animation: spin 1s ease-in-out infinite;
        -webkit-animation: spin 1s ease-in-out infinite;
      }

      @keyframes spin {
        to {
          -webkit-transform: rotate(360deg);
        }
      }
      @-webkit-keyframes spin {
        to {
          -webkit-transform: rotate(360deg);
        }
      }
    </style>
  </head>
  <body>
    <ul id="list"></ul>
    <div id="observer"></div>

    <script>
      let count = 0;
      const $ul = document.getElementById('list');
      function makeListElement() {
        Array(10)
          .fill(0)
          .forEach(() => {
            const $li = document.createElement('li');
            $li.textContent = ++count;
            $ul.appendChild($li);
          });
      }
      
      makeListElement();

      let timer;
      const $observer = document.getElementById('observer');
      const io = new IntersectionObserver((entries) => {
        clearTimeout(timer);
        if (entries[0].isIntersecting) {
          timer = setTimeout(() => makeListElement(), 1000);
        }
      });
      io.observe($observer);
    </script>
  </body>
</html>

 

 

저작자표시 (새창열림)

'Frontend' 카테고리의 다른 글

React-Query 정리  (0) 2022.08.05
[Javascript] Throttle 과 Debounce  (0) 2022.05.12
[Redux] 간단한 예제로 살펴보는 리덕스의 동작 원리  (0) 2022.03.17
[React] ContextAPI & useContext Hook을 통한 Global State 값 관리하기  (0) 2022.03.11
[Javascript] 변수, 호이스팅, TDZ  (0) 2022.03.07
    'Frontend' 카테고리의 다른 글
    • React-Query 정리
    • [Javascript] Throttle 과 Debounce
    • [Redux] 간단한 예제로 살펴보는 리덕스의 동작 원리
    • [React] ContextAPI & useContext Hook을 통한 Global State 값 관리하기
    pebblepark
    pebblepark
    프론트엔드 개발자입니다. 피드백은 언제나 환영입니다:)

    티스토리툴바