ํฐ์คํ ๋ฆฌ ๋ทฐ
๋ฆฌ์กํธ ์์์ Intersection Observer API๋ฅผ ์ฌ์ฉํ์ฌ Infinite Scroll ๊ธฐ๋ฅ ๊ตฌํ
choi95 2021. 12. 24. 16:52Infinite 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
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
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์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ์ด ๋๋ค.
ํด๋น ํค ๊ฐ ์ค ๊ต์ฐจ์ ์ด ๋์๋์ง ์๋ณํ ์ ์๋ ๊ฐ์ธ 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/178