ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

Infinite Scroll ๊ธฐ๋Šฅ์˜ ํ•„์š”์„ฑ

์›๋ž˜๋Š” ์ง„ํ–‰ํ•˜๋˜ ํ”„๋กœ์ ํŠธ์˜ ๋ฉ”์ธ ํŽ˜์ด์ง€ ๋‚ด์—์„œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์™€ ๋žœ๋”๋งํ•˜๋Š” ๋ฐฉ์‹์€ Carousel Slide ์˜€์ง€๋งŒ ๊ด€๋ จ ๋””์ž์ธ ์‹œ์•ˆ์ด ๋Œ€ํญ ์ˆ˜์ •๋˜์—ˆ๋‹ค.

 

์ด์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์ด ํŠน์ • ์ง€์ ์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ, ์ด๋ฅผ ์ธ์‹ํ•ด ์„œ๋ฒ„์—์„œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” Infinite Scroll ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.

 

์ฒ˜์Œ์—๋Š” ํŠน์ • ์ง€์ ์„ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•ด ScrollingElement ๋‚ด์˜ scrollTop๊ฐ’์„ ์‚ฌ์šฉํ•˜์˜€์ง€๋งŒ, ์Šคํฌ๋กค์ด ๋˜๋Š” ์†๋„์— ๋”ฐ๋ผ ๊ฐ’์ด ํ•ญ์ƒ ๋ฐ”๋€Œ๋Š” ์ด์Šˆ์™€ scroll event ์ž์ฒด์˜ ์„ฑ๋Šฅ ์ €ํ•˜ ๋ฌธ์ œ(๋™๊ธฐ์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜)๋กœ ์ธํ•ด Intersection Observer API๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

Intersection Observer API

https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API

 

Intersection Observer API - Web API | MDN

Intersection Observer API๋Š” ํƒ€๊ฒŸ ์š”์†Œ์™€ ์ƒ์œ„ ์š”์†Œ ๋˜๋Š” ์ตœ์ƒ์œ„ document ์˜ viewport ์‚ฌ์ด์˜ intersection ๋‚ด์˜ ๋ณ€ํ™”๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๊ด€์ฐฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.Intersection Observer API๋Š” ํƒ€๊ฒŸ ์š”์†Œ์™€ ์ƒ์œ„ ์š”์†Œ ๋˜๋Š”

developer.mozilla.org

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.

 

Intersection Observer API ๋Š” ๋ฃจํŠธ ์š”์†Œ์™€ ํƒ€๊ฒŸ ์š”์†Œ์˜ ๊ต์ฐจ์ ์„ ๊ด€์ฐฐํ•˜๊ณ  ํƒ€๊ฒŸ ์š”์†Œ๊ฐ€ ๋ฃจํŠธ ์š”์†Œ์™€ ๊ต์ฐจํ•˜๋Š”์ง€ ์•„๋‹Œ์ง€๋ฅผ ๊ตฌ๋ณ„ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

 

๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ ๋‹จ์— ๋„๋‹ฌํ–ˆ์„ ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด๋ฏ€๋กœ, ํƒ€๊ฒŸ์— ํ•ด๋‹นํ•˜๋Š” ์š”์†Œ๋ฅผ mainForm ์ปดํฌ๋„ŒํŠธ์— ์ถ”๊ฐ€ํ˜”๋‹ค.

<div ref={target} className="targetElement">{isLoaded && <Loader />}</div>

 

  • useRef๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ์š”์†Œ๋ฅผ ํƒ€๊ฒŸํŒ…ํ•˜์—ฌ ์ดํ›„ useEffect Hook์—์„œ ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ๋ฐ ์—…๋ฐ์ดํŠธ ์‹œ Intersection Observer API ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
  • Intersection Observer API ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์‹œ ์ธ์ž ๊ฐ’์œผ๋กœ ๋„˜๊ฒจ์ฃผ๊ธฐ ์œ„ํ•ด ํด๋ž˜์Šค๋ช… ์ง€์ •
  • ํŠน์ • ์ง€์ ์— ๋‹ฟ์„ ๊ฒฝ์šฐ, ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉฐ ์ด๋Š” ๋น„๋™๊ธฐ๋กœ ์ผ์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ์‹œ๊ฐ„์ด ์†Œ์š”๋  ์ˆ˜ ์žˆ๊ณ  ์ด๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋กœ๋”ฉ UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด isLoaded state๊ฐ’์ด true์ผ ๊ฒฝ์šฐ์— Loader ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋„๋ก ์„ค์ •

Loader ์ปดํฌ๋„ŒํŠธ๋Š” ํ”„๋กœ์ ํŠธ ์ „๋ฐ˜์— ์“ฐ์ด๊ธฐ ๋•Œ๋ฌธ์— react-loading์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ„๋„์˜ ๋ชจ๋“ˆ๋กœ ์ž‘์„ฑํ•ด๋‘์—ˆ๋‹ค.

https://www.npmjs.com/package/react-loading

 

react-loading

React loading component

www.npmjs.com

import { memo } from "react";
import ReactLoading from "react-loading";
import styled from "styled-components";

const Loader = () => {
  return (
    <Container>
      <ReactLoading type="spin" color="#262f6a" /> //๋กœ๋”ฉ ํƒ€์ž…๊ณผ ์ƒ‰์ƒ์„ props๋กœ ์ง€์ •
    </Container>
  );
};

const Container = styled.div`
  width: 100%;
  height: 80%;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
`;

export default memo(Loader);

 

๋‹ค์Œ์œผ๋ก  ํ•ด๋‹น ํƒ€๊ฒŸ ์š”์†Œ์— ๋Œ€ํ•œ Intersection Observer API ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€๋‹ค.

useEffect(() => {
    if (isIntersect && isLoaded) return;
    let observer;
    if (target.current) {
      observer = new IntersectionObserver(onIntersect);
      observer.observe(target.current);
    }

    return () => observer && observer.disconnect();
  }, [target.current, isIntersect, isLoaded]);

Container Component์ธ mainFormContainer ๋‚ด์—์„œ ์ „๋‹ฌ ๋ฐ›์€ isLoaded state๊ฐ’๊ณผ isIntersect ๊ฐ’์ด true์ผ ๊ฒฝ์šฐ์—๋Š” Oberver Intersection API ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  observer ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ์—ฐ์‚ฐ ๊ณผ์ •์„ ๋ฐฉ์ง€ํ•จ์œผ๋กœ์จ, ๊ต์ฐจ์ ์— ๋‹ฟ์•˜์„ ๊ฒฝ์šฐ์— ํ›„์† ๊ณผ์ •์ด ์—ฐ๋‹ฌ์•„ ์ผ์–ด๋‚˜์ง€ ์•Š๋„๋ก ํ•˜์˜€๋‹ค.

 

(target.current๋ฅผ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ๋„ฃ์—ˆ์„ ๊ฒฝ์šฐ์—, ์ฝ˜์†”์— waring message๋กœ ์ƒ์‹œ์ ์œผ๋กœ ๋ฐ”๋€Œ๋Š” ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ง๋ผ๊ณ  ๋œฌ๋‹ค. ์ดํ›„ ๋ฆฌํŒฉํ† ๋ง ์‹œ ํ•ด๋‹น ๋ถ€๋ถ„ ๋˜ํ•œ ๋ฆฌํŒฉํ† ๋งํ•  ์ƒ๊ฐ์ด๋‹ค)

 

๊ทธ๋ฆฌ๊ณ  ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์‹œ ํ• ๋‹นํ•ด์ค€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ธ onIntersect๋Š” ์‹ค์ œ redux-saga๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์—ญํ• ์„ ํ•˜๋Š” container component์ธ mainFormContainer์— ์„ ์–ธํ•ด ์ฃผ์—ˆ๋‹ค.

const onIntersect = async ([{ isIntersecting, target }], observer) => {
    if (isIntersecting) {
      observer.unobserve(target);
      dispatch(increaseOffset());
      setIntersect(true);
    }
  };

entries ๋Š” IntersectionObserEntry ์ธ์Šคํ„ด์Šค๋ฅผ ๋‹ด์€ ๋ฐฐ์—ด์ด๋ฉฐ ์œ„์™€ callback func์— ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ์ด ๋œ๋‹ค.

&amp;nbsp;IntersectionObserEntry

ํ•ด๋‹น ํ‚ค ๊ฐ’ ์ค‘ ๊ต์ฐจ์ ์ด ๋˜์—ˆ๋Š”์ง€ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ธ isIntersecting๊ณผ ํƒ€๊ฒŸ ์š”์†Œ๋ฅผ ๋””์ŠคํŠธ๋Ÿญ์ณ๋ง ํ• ๋‹น์„ ํ†ตํ•ด ํ•„์š”์— ๋งž๊ฒŒ ๊ฐ€์ ธ์™”๋‹ค.

๋งŒ์•ฝ์— isIntersection ๊ฐ’์ด true์ผ ๊ฒฝ์šฐ์—๋Š” ์šฐ์„  ํƒ€๊ฒŸ ์š”์†Œ๋ฅผ unobserver ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํ• ๋‹น์„ ํ•ด์ œํ•ด์ฃผ์–ด ์ค‘๋ณต์„ ๋ฐฉ์ง€ํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  dispatch()๋ฅผ ํ†ตํ•ด ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ currentOffset ๊ฐ’์„ ๊ฐ€์‚ฐํ•ด ์ฃผ์—ˆ๋‹ค.

 

useSelector๋ฅผ ํ†ตํ•ด redux-store ๋‚ด์—์„œ currentOffset๊ฐ’์„ ์ฐธ์กฐํ•˜๊ณ  ํ•ด๋‹น ๊ฐ’์ด ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์„ ๊ฒฝ์šฐ, ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก useEffect ๋‚ด์— ๊ด€๋ จ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.

const currentOffset = useSelector(({ mainForm }) => mainForm.currentOffset);

	(...)

useEffect(() => {
    async function getMoreContents() {
      if (!isIntersect) return;

      dispatch(isLoaded());

      await new Promise(resolve => setTimeout(resolve, 1500));

      if (sort === "friends") {
        batch(() => {
          dispatch(friends(currentOffset));
        });
      } else {
        batch(() => {
          dispatch(content({ sort, currentOffset }));
          dispatch(rooms({ sort, currentOffset }));
        });
      }

      dispatch(isLoaded());
      setIntersect(false);
    }
    getMoreContents();
  }, [isIntersect, currentOffset]);

 

๊ฒฐ๊ณผํ™”๋ฉด

 

์ฐธ๊ณ  ํฌ์ŠคํŒ…

https://choi95.tistory.com/56

 

Infinite Scroll

Question 1. ์ตœ์ดˆ์—๋Š” 20๊ฐœ์˜ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. 2. ์Šคํฌ๋กค์„ ์ตœํ•˜๋‹จ์œผ๋กœ ์ด๋™์‹œ 'loading' ์ƒํƒœ ํ‘œ์‹œ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋ฉฐ, ์ดํ›„์˜ 20๊ฐœ ๋ชฉ๋ก์„ ๋” ๊ฐ€์ ธ์˜จ๋‹ค. 3. ๋กœ๋”ฉ ์™„๋ฃŒ์‹œ 'loading'ํ‘œ์‹œ๊ฐ€ ์‚ฌ๋ผ์ง€๋ฉฐ, ๊ฐ€์ ธ์˜จ ๋ชฉ๋ก

choi95.tistory.com

https://choi95.tistory.com/178

 

ํŠน์ • ๊ฐ’์— Entry ์‹œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ_Infinite Scroll

๊ตฌํ˜„ ๊ฒฐ๊ณผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด Scroll๊ฐ’์ด ํ™”๋ฉด ์ƒ์— ๋ ์ง€์ ์— ๋„๋‹ฌํ–ˆ์„ ๊ฒฝ์šฐ์— ์ถ”๊ฐ€์ ์œผ๋กœ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜์—ฌ ํ™”๋ฉด์— ๋žœ๋”๋ง๋˜๋„๋ก ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜์˜€๋‹ค. ๋ฆฌ์•กํŠธ ์ƒ์—์„œ ์ „์—ญ ๊ฐ์ฒด(Window)์— ์Šคํฌ๋กค

choi95.tistory.com

https://choi95.tistory.com/57

 

Debounce ํ•จ์ˆ˜๋กœ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ตœ์ ํ™”

https://choi95.tistory.com/56 Infinite Scroll Question 1. ์ตœ์ดˆ์—๋Š” 20๊ฐœ์˜ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. 2. ์Šคํฌ๋กค์„ ์ตœํ•˜๋‹จ์œผ๋กœ ์ด๋™์‹œ 'loading' ์ƒํƒœ ํ‘œ์‹œ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋ฉฐ, ์ดํ›„์˜ 20๊ฐœ ๋ชฉ๋ก์„ ๋” ๊ฐ€์ ธ์˜จ๋‹ค. 3. ๋กœ๋”ฉ ์™„๋ฃŒ..

choi95.tistory.com

 

๋Œ“๊ธ€
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2024/12   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ