Javascript

[Vanilla Javascript] ๋ฌดํ•œ ์Šคํฌ๋กค(infinite scroll) ๊ตฌํ˜„ํ•˜๊ธฐ

createElement 2022. 11. 25. 02:00

๐Ÿค” ๋ฌดํ•œ ์Šคํฌ๋กค์ด๋ž€?

์ปจํ…์ธ ๋ฅผ ํŽ˜์ด์ง•ํ•˜๋Š” ๊ธฐ๋ฒ• ์ค‘ ํ•˜๋‚˜๋กœ ์Šคํฌ๋กค์„ ์ด์šฉํ•ด
๋งจ ์•„๋ž˜๊นŒ์ง€ ๋„๋‹ฌํ•  ๋•Œ ์ƒˆ๋กœ์šด ์ปจํ…์ธ ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ์‹์„ ๋งํ•œ๋‹ค.

 

๐Ÿช„ ๊ตฌํ˜„ ๋ฐฉ์‹

์Šคํฌ๋กค์„ ๋๊นŒ์ง€ ๋‚ด๋ ธ์„ ๋•Œ fetch๋œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์—˜๋ฆฌ๋จผํŠธ์— ๊ณ„์† ์ถ”๊ฐ€(appendChild)ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.
๊ตฌํ˜„ ๋ฐฉ์‹์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€์ด๋‹ค.

 

1. scroll ์ด๋ฒคํŠธ๋ฅผ ์ด์šฉํ•œ ๋ฐฉ๋ฒ•

- ์ „ํ†ต์ ์ธ ๋ฐฉ์‹

- window์˜ scroll ์ด๋ฒคํŠธ๊ฐ€ ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด์˜ ๋†’์ด, ์Šคํฌ๋กค๋ฐ”์˜ ์œ„์น˜๋ฅผ ์ด์šฉํ•ด body(์ปจํ…์ธ )์˜ ๋๊นŒ์ง€ ๋‹ค๋‹ค๋ž์œผ๋ฉด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์„ ๋งํ•œ๋‹ค.

 

- window.innerHeight : ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด์—ฌ์ง€๋Š” ํ™”๋ฉด ์ค‘ ํƒญ, url ์ฃผ์†Œ์ฐฝ, ๋ถ๋งˆํฌ ํƒญ์„ ์ œ์™ธํ•œ ๋ธŒ๋ผ์šฐ์ €์˜ ๋†’์ด

- window.scrollY : ํ˜„์žฌ ์Šคํฌ๋กค๋ฐ” ์œ„์น˜

- document.body.offsetHeight : body์˜ height

- window.innerHeight + window.scrollY : (ํƒญ, url ์ฃผ์†Œ์ฐฝ, ๋ถ๋งˆํฌ ํƒญ์„ ์ œ์™ธํ•œ)๋ธŒ๋ผ์šฐ์ €์˜ ๋†’์ด + ์‚ฌ์šฉ์ž์˜ ์Šคํฌ๋กค Y ๊ฐ’

- window.innerHeight + window.scrollY ๊ฐ’์ด ํ˜„์žฌ body์˜ ๋†’์ด ๊ฐ’๋ณด๋‹ค ๊ฐ™๊ฑฐ๋‚˜ ํฐ ๊ฒฝ์šฐ ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

window.addEventListener("scroll", () => {
  // 100์„ ๋”ํ•˜๋ฉด ์Šคํฌ๋กค์„ ๋๊นŒ์ง€ ๋‚ด๋ฆฌ๊ธฐ 100px ์ „์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
  const isScrollEnded =
    window.innerHeight + window.scrollY + 100 >= document.body.offsetHeight;

  // totalCount : api์˜ ๋ฐ์ดํ„ฐ์˜ ์ „์ฒด ๊ธธ์ด
  // photos.length: ํ˜„์žฌ ๋žœ๋”๋ง๋œ ๋ฐ์ดํ„ฐ์˜ ๊ธธ์ด
  const { photos, totalCount, isLoading } = this.state;
  if (isScrollEnded && !isLoading && photos.length < totalCount) {
    onScrollEnded();
  }
});

 

 

๐Ÿ“ฃ isLoading์˜ ์ƒํƒœ ์กด์žฌ ์ด์œ 

๋ฐ์ดํ„ฐ๋ฅผ ์ด๋ฏธ ์š”์ฒญํ–ˆ์Œ์—๋„ ๊ณ„์† ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. 

์ด ๊ฒฝ์šฐ isLoading ์ƒํƒœ๋ฅผ ๋‘์–ด ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๊ณ  ์‘๋‹ต๋ฐ›๊ธฐ ์ „๊นŒ์ง€๋Š” ์žฌ์š”์ฒญ์„ ํ•˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ“ฃ totalCount์˜ ์กด์žฌ ์ด์œ 

์ด๋ฏธ api์˜ ๋งˆ์ง€๋ง‰ ๋ฐ์ดํ„ฐ๊นŒ์ง€ ๋ชจ๋‘ ๊ฐ€์ ธ์™”์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  1) ์Šคํฌ๋กค์„ ๋งจ ์•„๋ž˜ ๋‚ด๋ฆฐ ์ƒํƒœ, 2) isLoading์ด false์ธ ๊ฒฝ์šฐ, ์ด ๋‘ ๊ฐ€์ง€๊ฐ€ ์ฐธ์ผ ๋•Œ ๊ณ„์† ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.

์ด ๊ฒฝ์šฐ api์˜ ๋ฐ์ดํ„ฐ ์ „์ฒด ๊ฐœ์ˆ˜(totalCount)์™€ ํ˜„์žฌ๊นŒ์ง€ ๋žœ๋”๋ง๋œ ๋ฆฌ์ŠคํŠธ์˜ ๊ฐœ์ˆ˜(photos.length)๋ฅผ ๋น„๊ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.



 

 

2. Intersection Observer๋ฅผ ์ด์šฉํ•œ ๋ฐฉ๋ฒ•

- ์ตœ๊ทผ์— ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹

- observe, unobserve๋ฅผ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„ํ•œ๋‹ค.

- threshold ๊ฐ’์œผ๋กœ observe ๋Œ€์ƒ์ด ์–ผ๋งˆ๋‚˜ ๋…ธ์ถœ๋˜์—ˆ๋Š”์ง€์— ๋”ฐ๋ผ ๋™์ž‘ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Intersection Observer ์„ค์ •

์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ฝœ๋ฐฑ ํ•จ์ˆ˜, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ observer์˜ ์„ค์ • ์‚ฌํ•ญ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

- root: ๊ด€์ฐฐ ๋Œ€์ƒ์ด ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด์— ๋“ค์–ด์™”์Œ์„ ๊ฐ์ง€ํ•˜๋Š” ์˜์—ญ์ด๋‹ค.

- rootMargin: ๊ด€์ฐฐ ๋Œ€์ƒ์„ ๊ฐ์ง€ํ•˜๋Š” ์˜์—ญ์„ margin์„ ์ด์šฉํ•ด ๋ฒ”์œ„๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ '0px 0px 0px 0px'์ด๋ฉฐ, ๋ฐ˜๋“œ์‹œ ๋ฌธ์ž์—ด๋กœ ๋‹จ์œ„์™€ ํ•จ๊ป˜ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

- threshold: ๊ด€์ฐฐ ๋Œ€์ƒ์ด ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด ๋‚ด์— ์–ผ๋งˆ๋‚˜ ๋ณด์˜€์„ ๋•Œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ฐ’์„ ๋งํ•œ๋‹ค.

// ์ฒซ๋ฒˆ์งธ ์ธ์ž๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” ์„ค์ •
const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      const { photos, totalCount, isLoading } = this.state;
      // ๋กœ๋”ฉ ์ค‘์—๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•จ
      if (entry.isIntersecting && !isLoading) {
        if (photos.length < totalCount) {
          onScrollEnded();
        }
      }
    });
  },
  {
    // 1์ธ ๊ฒฝ์šฐ ์Šคํฌ๋กค์„ ์ด์šฉํ•ด ๊ด€์ฐฐ ๋Œ€์ƒ์ด ๋ชจ๋‘ ๋ณด์ธ ๊ฒฝ์šฐ์— ์ฝœ๋ฐฑ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.
    threshold: 1,
  }
);

// ์ค‘๋žต

this.render = () => {
  // ์ค‘๋žต

  let $lastLi = null;

  // ๋‹ค์Œ ๊ด€์ฐฐ ๋Œ€์ƒ์„ ๋งˆ์ง€๋ง‰์˜ ๋ฆฌ์ŠคํŠธ ์š”์†Œ๋กœ ํ•œ๋‹ค.
  const $nextLi = $photos.querySelector("li:last-child");

  if ($nextLi !== null) {
    if ($lastLi !== null) {
      // ๋งˆ์ง€๋ง‰ ์š”์†Œ๊ฐ€ null์ธ ๊ฒฝ์šฐ์—๋Š” ๊ด€์ฐฐํ•˜์ง€ ์•Š๋Š”๋‹ค.
      observer.unobserve($lastLi);
    }

    $lastLi = $nextLi;
    observer.observe($lastLi); // ๊ด€์ฐฐ ๋Œ€์ƒ ๋“ฑ๋ก
  }
};