Frontend/JavaScript

[JavaScript] λ¬΄ν•œ 슀크둀 κ΅¬ν˜„ν•˜κΈ°(Intersection Observer API)

_μ„±ν˜Έ_ 2023. 5. 19. 03:16
728x90
λ°˜μ‘ν˜•

λ¬΄ν•œ 슀크둀(Infinite Scroll)μ΄λž€? μ‚¬μš©μžκ°€ νŽ˜μ΄μ§€ ν•˜λ‹¨μ— λ„λ‹¬ν–ˆμ„ λ•Œ, μ½˜ν…μΈ κ°€ 계속 λ‘œλ“œλ˜λŠ” μ‚¬μš©μž κ²½ν—˜(UX) 방식이닀. ν•œ νŽ˜μ΄μ§€ μ•„λž˜λ‘œ 슀크둀 ν•˜λ©΄ 끝없이 μƒˆλ‘œμš΄ 화면을 λ³΄μ—¬μ£Όκ²Œ 되고 이둜 인해 λ§Žμ€ μ–‘μ˜ μ½˜ν…μΈ λ₯Ό 슀크둀 ν•΄μ„œ λ³Ό 수 μžˆλ‹€.

 

νŽ˜μ΄μ§€λ„€μ΄μ…˜μ΄ μ•„λ‹Œ λ¬΄ν•œ 슀크둀둜 κ΅¬ν˜„ν•˜λ €λŠ” μ΄μœ β“

μƒν’ˆ λͺ©λ‘μ—μ„œ μƒν’ˆμ„ λ³΄μ—¬μ£Όκ³ μž ν•  λ•Œ νŽ˜μ΄μ§€λ„€μ΄μ…˜, λ¬΄ν•œ 슀크둀 쀑 무엇이 μ‚¬μš©μž κ²½ν—˜μ„ λ”μš± μ’‹κ²Œ ν•  수 μžˆμ„μ§€ κ³ λ―Όν•œ 끝에 λͺ¨λ°”일 ν™˜κ²½μ„ κ³ λ €ν•΄μ„œ λ¬΄ν•œ μŠ€ν¬λ‘€μ„ μ‚¬μš©ν•˜κΈ°λ‘œ ν–ˆλ‹€.(차후에 ν”„λ‘œμ νŠΈλ₯Ό λ°˜μ‘ν˜•μœΌλ‘œ λ¦¬νŒ©ν„°λ§ ν•  κ³„νšπŸ™‡πŸ»)

λ˜ν•œ, λ‹€μŒ μƒν’ˆμ„ 보기 μœ„ν•œ μ‚¬μš©μžμ˜ 클릭을 μ΅œμ†Œν™”ν•˜κ³  더 μ‰½κ²Œ λ‹€μ–‘ν•œ μƒν’ˆμ„ λ³Ό 수 μžˆμ„ 것이라고 νŒλ‹¨ν–ˆλ‹€.

 

κ΅¬ν˜„ν•˜λŠ” κ³Όμ •

λ¬΄ν•œ μŠ€ν¬λ‘€μ„ κ΅¬ν˜„ν•˜λŠ” 방법은 μŠ€ν¬λ‘€μ„ ν•  λ•Œλ§ˆλ‹€ 이벀트λ₯Ό λ°œμƒμ‹œν‚€λŠ” 방법(addEventListenerκ°€ μ€‘λ³΅ν•΄μ„œ μŒ“μ΄κ³  λ³΅μž‘ν•œ 둜직이 μ„žμ—¬ 있으면 μ„±λŠ₯ μ΄μŠˆκ°€ λ°œμƒ) λ“± λ‹€μ–‘ν•˜λ‹€. κ·Έμ€‘μ—μ„œ λ‚˜λŠ” Intersection Observer API의 IntersectionObserver μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ΄μš©ν•΄μ„œ κ΅¬ν˜„ν•˜μ˜€λ‹€.

 

🧐 IntersectionObserver μΈν„°νŽ˜μ΄μŠ€λž€?

기본적으둜 λΈŒλΌμš°μ € 뷰포트(Viewport)와 μ„€μ •ν•œ μš”μ†Œ(Element)의 ꡐ차점을 κ΄€μ°°ν•˜λ©°, μš”μ†Œκ°€ λ·°ν¬νŠΈμ— ν¬ν•¨λ˜λŠ”μ§€, ν¬ν•¨λ˜μ§€ μ•ŠλŠ”μ§€, 더 μ‰½κ²ŒλŠ” μ‚¬μš©μž 화면에 μ§€κΈˆ λ³΄μ΄λŠ” μš”μ†ŒμΈμ§€ μ•„λ‹Œμ§€λ₯Ό κ΅¬λ³„ν•˜λŠ” κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€.

이 κΈ°λŠ₯은 λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λ˜κΈ° λ•Œλ¬Έμ—, scroll 같은 이벀트 기반의 μš”μ†Œ κ΄€μ°°μ—μ„œ λ°œμƒν•˜λŠ” λ Œλ”λ§ μ„±λŠ₯μ΄λ‚˜ 이벀트 연속 호좜 같은 문제 없이 μ‚¬μš©ν•  수 μžˆλ‹€.

 

1) IntersectionObserver μΈμŠ€ν„΄μŠ€ 생성

  • new IntersectionObserver()λ₯Ό 톡해 μƒμ„±ν•œ μΈμŠ€ν„΄μŠ€(observer)둜 κ΄€μ°°μžλ₯Ό μ΄ˆκΈ°ν™”ν•˜κ³  κ΄€μ°°ν•  λŒ€μƒ(element)을 μ§€μ •ν•œλ‹€.
  • μƒμ„±μžλŠ” 2개의 인수(callback, options)λ₯Ό 가진닀.
const observer = new IntersectionObserver(callback, options); // κ΄€μ°°μž μ΄ˆκΈ°ν™”
observer.observe(element); // κ΄€μ°°ν•  λŒ€μƒ(μš”μ†Œ) 등둝
// observer.unobserve(element); // νŠΉμ • λŒ€μƒ(μš”μ†Œ)에 λŒ€ν•œ κ΄€μ°° 쀑단

 

2) callback ν•¨μˆ˜ λ§Œλ“€κΈ°

  • κ΄€μ°°ν•  λŒ€μƒ(target)이 λ“±λ‘λ˜κ±°λ‚˜ κ°€μ‹œμ„±(λ³΄μ΄λŠ”μ§€, 보이지 μ•ŠλŠ”μ§€)에 λ³€ν™”κ°€ 생기면 κ΄€μ°°μžλŠ” 콜백(callback)을 μ‹€ν–‰ν•œλ‹€.
  • μ½œλ°±μ€ 2개의 인수(entries, observer)λ₯Ό 가진닀.
const callback = (entries, observer) => {};

 

entries

  • IntersectionObserverEntry μΈμŠ€ν„΄μŠ€μ˜ 배열이닀.
  • IntersectionObserverEntryλŠ” 읽기 μ „μš©μ˜ 속성듀을 ν¬ν•¨ν•œλ‹€. μ—¬λŸ¬ 속성듀이 μ‘΄μž¬ν•˜μ§€λ§Œ 직접 μ‚¬μš©ν•œ isIntersecting은 κ΄€μ°° λŒ€μƒμ˜ ꡐ차 μƒνƒœ(Boolean)λ₯Ό μ•Œλ €μ£ΌλŠ” 속성이닀. λ‚˜λ¨Έμ§€ 속성듀은 λ‹€μŒ 링크λ₯Ό μ°Έκ³ ν•΄λ³΄μžβ—οΈ

 

observer

콜백이 μ‹€ν–‰λ˜λŠ” ν•΄λ‹Ή μΈμŠ€ν„΄μŠ€λ₯Ό μ°Έμ‘°ν•œλ‹€.

const callback = (entries, observer) => {
    console.log(observer);
};

 

3) options 객체 λ§Œλ“€κΈ°

const options = {
    root: null,
    rootMargin: '0px 0px 0px 0px',
    threshold: 0,
};

 

root

  • νƒ€μΌ“μ˜ κ°€μ‹œμ„±μ„ κ²€μ‚¬ν•˜κΈ° μœ„ν•΄ 뷰포트 λŒ€μ‹  μ‚¬μš©ν•  μš”μ†Œ 객체(루트 μš”μ†Œ)λ₯Ό μ§€μ •ν•œλ‹€.
  • 기본값은 null(뷰포트) 이닀.

 

rootMargin

  • λ°”κΉ₯ μ—¬λ°±(margin)을 μ΄μš©ν•΄ root λ²”μœ„λ₯Ό ν™•μž₯ν•˜κ±°λ‚˜ μΆ•μ†Œν•  수 μžˆλ‹€.
  • css의 marginκ³Ό 같이 4λ‹¨κ³„λ‘œ 여백을 μ„€μ •ν•  수 있으며, px λ˜λŠ” %둜 λ‚˜νƒ€λ‚Ό 수 μžˆλ‹€.
  • 기본값은 '0px 0px 0px 0px'이며 λ‹¨μœ„λ₯Ό κΌ­ μž…λ ₯ν•΄μ•Ό ν•œλ‹€.

 

threshold

  • μ˜΅μ €λ²„κ°€ μ‹€ν–‰λ˜κΈ° μœ„ν•΄ νƒ€μΌ“μ˜ κ°€μ‹œμ„±μ΄ μ–Όλ§ˆλ‚˜ ν•„μš”ν•œμ§€ λ°±λΆ„μœ¨λ‘œ ν‘œμ‹œν•œλ‹€.
  • 기본값은 Array νƒ€μž…μ˜ [0]μ΄μ§€λ§Œ Number νƒ€μž…μ˜ 단일 κ°’μœΌλ‘œλ„ μž‘μ„±ν•  수 μžˆλ‹€.
    • 0: νƒ€μΌ“μ˜ κ°€μž₯자리 픽셀이 root λ²”μœ„λ₯Ό κ΅μ°¨ν•˜λŠ” μˆœκ°„(νƒ€μΌ“μ˜ κ°€μ‹œμ„±μ΄ 0%일 λ•Œ) μ˜΅μ €λ²„κ°€ μ‹€ν–‰λœλ‹€.
    • 0.3: νƒ€μΌ“μ˜ κ°€μ‹œμ„±μ΄ 30%일 λ•Œ μ˜΅μ €λ²„κ°€ μ‹€ν–‰λœλ‹€.
    • [0, 0.3, 1]: νƒ€μΌ“μ˜ κ°€μ‹œμ„±μ΄ 0%, 30%, 100%일 λ•Œ λͺ¨λ‘ μ˜΅μ €λ²„κ°€ μ‹€ν–‰λœλ‹€.

 

πŸ§‘πŸ»‍πŸ’» κ΅¬ν˜„ν•œ 전체 μ½”λ“œ

/*
<div>
  <ul></ul>
  <div id="listEnd"></div>
</div>
*/

const listEnd = document.querySelector('#listEnd'); // κ΄€μ°°ν•  λŒ€μƒ(μš”μ†Œ)
const options = {
    root: null, // 뷰포트λ₯Ό κΈ°μ€€μœΌλ‘œ νƒ€μΌ“μ˜ κ°€μ‹œμ„± 검사
    rootMargin: '0px 0px 0px 0px', // ν™•μž₯ λ˜λŠ” μΆ•μ†Œ X
    threshold: 0, // νƒ€μΌ“μ˜ κ°€μ‹œμ„± 0%일 λ•Œ μ˜΅μ €λ²„ μ‹€ν–‰
};

let page = 0;
const onIntersect = (entries, observer) => {
  entries.forEach(async (entry) => {
    if (entry.isIntersecting) {
      console.log('λ¬΄ν•œ 슀크둀 μ‹€ν–‰');
      page++;
      console.log('page: ' + page);
      const products = await getData( // μƒν’ˆμ˜ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” ν•¨μˆ˜
        page,
        urlCategoryId,
        urlKeywordId,
        urlSortId
      );

      if (products.length < 1) {
          if (page === 1) productList.innerHTML = noSearchResultPage;
          observer.unobserve(listEnd); // νŠΉμ • λŒ€μƒ(μš”μ†Œ)에 λŒ€ν•œ κ΄€μ°° 쀑단
          return;
      }

      productList.insertAdjacentHTML(
        'beforeend',
        products.map(createTicket).join('')
      );
    }
  });
};
  
const observer = new IntersectionObserver(onIntersect, options); // κ΄€μ°°μž μ΄ˆκΈ°ν™”
observer.observe(listEnd); // κ΄€μ°°ν•  λŒ€μƒ(μš”μ†Œ) 등둝

 

πŸ“š μ°Έκ³ ν•œ μ‚¬μ΄νŠΈ

 

Intersection Observer - μš”μ†Œμ˜ κ°€μ‹œμ„± κ΄€μ°°

Intersection observerλŠ” 기본적으둜 λΈŒλΌμš°μ € 뷰포트(Viewport)와 μ„€μ •ν•œ μš”μ†Œ(Element)의 ꡐ차점을 κ΄€μ°°ν•˜λ©°, μš”μ†Œκ°€ λ·°ν¬νŠΈμ— ν¬ν•¨λ˜λŠ”μ§€ ν¬ν•¨λ˜μ§€ μ•ŠλŠ”μ§€, 더 μ‰½κ²ŒλŠ” μ‚¬μš©μž 화면에 μ§€κΈˆ λ³΄μ΄λŠ” μš”μ†ŒμΈ

heropy.blog

 

Intersection Observer API - Web APIs | MDN

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

developer.mozilla.org

 

javascript . μ–΄λ–»κ²Œ λ¬΄ν•œ μŠ€ν¬λ‘€μ„ κ΅¬ν˜„ν•˜μ‹œλ‚˜μš”?

μ•ˆλ…•ν•˜μ„Έμš”. μ—¬λŸ¬λΆ„μ€ λ¬΄ν•œ μŠ€ν¬λ‘€μ„ μ§€κΈˆκΉŒμ§€ μ–΄λ–€ μ‹μœΌλ‘œ κ΅¬ν˜„ν•˜μ…¨λ‚˜μš”? μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œλŠ” λ‹€μ–‘ν•œ λ°©λ²•μœΌλ‘œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 직접 κ΅¬ν˜„ν•˜λŠ” μ‚¬λžŒλ„ 있고 라이브러리λ₯Ό μ‚¬μš©ν•˜λŠ” μ‚¬λžŒλ„

talkwithcode.tistory.com