ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋์ ์์๊ฐ *๋ทฐํฌํธ์ ๊ต์ฐจํ๋ ๋ณ๊ฒฝ์ฌํญ์ ๊ด์ฐฐํด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค. ์ฆ ํ๋ฉด(๋ทฐํฌํธ)์์ ์ฐ๋ฆฌ๊ฐ ์ง์ ํ ํ๊ฒ ์๋ ๋ฉํธ๊ฐ ๋ณด์ด๊ณ ์๋์ง๋ฅผ ๊ด์ฐฐํ๋ API์ ๋๋ค.
debounce์ย throttle์ ์คํฌ๋กค ์ด๋ฒคํธ๋ก ์ธํด ๋ฐ์ํ๋ ๋ถํ์ํ ํจ์ ํธ์ถ ์๋ฅผ ์ปจํธ๋กคํ๋ ๋ฐฉ๋ฒ๋ค์ธ๋ฐ ์ด๋ค์ด ํ์ํ ์ด์ ๋
window.addEventListener('scroll', function() {
return console.log('scroll!');
});
์์ ์ฝ๋๋ฅผ ์ฝ์ ์ฐฝ์ ์ ๋ ฅํ๊ณ ์คํฌ๋กค์ ์กฐ๊ธ๋ง ํด๋ณด๋ฉด ๋จ๋ฒ์ ์ดํดํ ์ ์๋ค. ์๋ก ํน์ ์๋๋ก ์คํฌ๋กค์ ํ ๋๋ง๋ค ํด๋น ํจ์๊ฐ ์๋ ์์ด ํธ์ถ๋ฉ๋๋ค. ๋์ฒด๋ก ์ด๋ฐ ํ์์ ๋ชฉ์ ์ผ๋ก ์คํฌ๋กค ์ด๋ฒคํธ๋ฅผ ๊ฑฐ๋ ๊ฒ์ด ์๋๊ธฐ ๋๋ฌธ์ ๊ฑท์ก์ ์ ์์ด ํธ์ถ๋๋ ํจ์๋ฅผ debounce์ throttle์ ์ฌ์ฉํ์ฌ ์ปจํธ๋กค ํ๊ฒ ๋ฉ๋๋ค.
์คํฌ๋กค ์ด๋ฒคํธ์์๋ ํ์ฌ์ ๋์ด ๊ฐ์ ์๊ธฐ ์ํดoffsetTop์ ์ฌ์ฉํ๋๋ฐ ์ ํํ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ ์ํด ๋งค๋ฒ layout์ ์๋ก ๊ทธ๋ฆฌ๊ฒ ๋ฉ๋๋ค. layout์ ์๋ก ๊ทธ๋ฆฐ๋ค๋ ๊ฒ์ ๋ ๋ ํธ๋ฆฌ๋ฅผ ์ฌ์์ฑํ๋ค๋ ๋ป์ธ๋ฐ, reflow๋ผ๊ณ ๋ ๋ถ๋ฆฌ๋ ์ด ๊ณผ์ ์ด ๋ฐ๋ณต๋๋ฉด ๋น์ฐํ ๋ธ๋ผ์ฐ์ ์ ์ฑ๋ฅ์ด ์ ํ๋๊ณ ํ๋ฉด์ ๋ฒ๋ฒ
๊ฑฐ๋ฆผ์ด ์๊ธธ ์ ๋ฐ์ ์์ต๋๋ค.
๋ทฐํฌํธ๋ก ๊ฐ์ฃผ๋๋ ๋์์ ๋๋ค.
ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ด์ฐฐํ ๋ทฐํฌํธ์ธ ๋ฃจํธ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ถ์ํ๊ธฐ๋ ๋๋ ค์ค๋๋ค.
์๋ฌด๋ฐ ์ค์ ์ ํ์ง ์์ผ๋ฉด deafult 0px 0px 0px 0px์ ๋๋ค.
๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ด์ฐฐํ๋ ๊ต์ฐจ ์์ญ์์์ ๋น์จ(์๊ณ๊ฐ)์ ๋งํฉ๋๋ค.
0~1๊น์ง์ ์ซ์๋ก ์ค์ ๋ฉ๋๋ค. ์ต๋ 1(100%)๊น์ง ์ง์ ๊ฐ๋ฅํ๋ฉฐ, 0.7์ด๋ผ ํ๋ฉด ๋์์ด ํ๋ฉด์ 70% ์ด์ ๋ณด์ด๊ธฐ ์์ํ ๋ ์ฝ๋ฐฑ ํจ์๋ฅผ ํธ์ถํฉ๋๋ค.
IntersectionObserver๊ฐ ๋์์ ๊ด์ฐฐํ๋ ๊ฒ์ ์ค์ง
IntersectionObserver๊ฐ ๊ด์ฐฐํ ๋์์ ๊ฐ๋ฅด์ณ์ค
IntersectionObserver๊ฐ ํน์ ์์์ ๊ด์ฐฐ์ ์ค์ง
IntersectionObserver๊ด์ฐฐ๋ ๋ชจ๋ ๋์์๋ํ ๋ฐฐ์ด์ ๋ฐํ
import { InView } from 'react-intersection-observer';
const Component = () => (
<InView>
{({ inView, ref, entry }) => (
<div ref={ref}>
<h2>{`Header inside viewport ${inView}.`}</h2>
</div>
)}
</InView>
);
๋ฅผ ์ฌ์ฉํ์ฌ InViewํจ์๋ฅผ ์ ๋ฌํฉ๋๋ค. ์์ ๊ฐ์ด ์ปดํผ๋ํธ๋ฅผ ๊ฐ์ธ๋ฉด ํด๋น ํจ์๊ฐ ๊ฐ์ผ ์ปดํผ๋ํธ์์์ ์ํ๊ฐ ๋ณ๊ฒฝ์ด๋๋ฉด ์๋ก์ด ๊ฐ์ด ํธ์ถ๋ฉ๋๋ค.
ํ์ฌ ๊ฐ ๊ด์ฐฐ๋๊ณ ์๋์ง์๋ํ boolean๊ฐ์ ๋๋ค.
๊ฐ ๊ฐ์ธ๊ณ ์๋ children์์์์ IntersectionObserver๊ฐ ๋ชจ๋ํฐ๋งํ ์์๋ฅผ ์ ํด์ค๋๋ค.
์ ๋๊ฐ๋ฅผ ์ ์ธํ๊ณ ์กฐ๊ธ๋ ์ธ๋ฐํ ์ค์ ์ ๋ณผ ์ ์์ต๋๋ค.

์์ ์คํฌ๋ฆฐ์ท์ IntersectionObserverEntry์ ์ฝ์๊ฐ ๊ฒฐ๊ณผ์ ๋๋ค.
intersectionObserver๊ฐ ๊ด์ฐฐํ๋ Root๋ฅผ ๊ต์ฐจํ ๋์ ์ํ๊ฐ ๋ณํ๋ฅผ boolean๊ฐ์ผ๋ก ํ์ํฉ๋๋ค.
๊ต์ฐจ๊ฐ์ด ๋ณํํ์๋์ timestamp๋ฅผ ๊ธฐ๋กํด์ค๋๋ค.
์ฝ๊ฒ ์๊ฐํ๋ฉด ์์ isIntersecting์ ์ํ๊ฐ ๋ณํํ ์๊ฐ์ ์๊ฐ์ ๋๋ค.
// components>featured>ItemList.tsx
import { InView } from 'react-intersection-observer'; // 1
const onTrackingViewItem = (inView: boolean, itemIdx: number, entry: any): any => {
if (inView) {
//ํ์ํ ๋ก์ง
}
};
<InView key={index}> // 2
{({ inView, ref, entry }) => ( // 3
<S.ItemContainer>
<S.ItemSection
ref={ref} // 4
onChange={onTrackingViewItem(inView, itemIdx, entry)} //5
onClick={() => onItemClick(item)}>
<img src={item.media.imageUrl} className="itemImg" alt="์ํ ์ด๋ฏธ์ง" />
<S.ItemTitle>{item.title}</S.ItemTitle>
<S.ItemPrice>{item.property.price.text}</S.ItemPrice>
<SellState sellState={sellState} alt="์ํ ์ํ ์ด๋ฏธ์ง" />
</S.ItemSection>
</S.ItemContainer>
)}
</InView>
itemlist๊ฐ ๋ฟ๋ ค์ง๋ฉด ํด๋น ์ด๋ฏธ์ง ๋ง๋ค ๊ด์ฐฐ์ด ํ์ํ๋ค.
์ ์ ๊ฐ ์ด๋ค ์ํ์ ๋ณด์๋์ง ์ด๋ค์ํ์ ํด๋ฆญํ์๋์ง ๋ฑ๋ฑ
ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ํ์ฌ ํ๋ก์ ํธ์์ ํ์ํ ํจ์๊ฐ ํฌํจ๋ ์ปดํผ๋ํธ ํธ์ถ์ ํด์ค๋๋ค.
importํด์จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ children์ผ๋ก ํ์ํ ์์๋ค์ธ๋ถ์์ ๊ฐ์ธ์ค๋๋ค.
ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ปดํผ๋ํธ์์ ํ์ํ๋ state๊ฐ์ ํธ์ถํ์ฌ children์ธ๋ถ์์ ๊ฐ์ธ์ค๋๋ค.
intersectionObserver๊ฐ ๊ด์ฐฐํ ๋ถ๋ถ์ ๋ช ์ํฉ๋๋ค.
๋ด๋ถ ํ๋ก์ ํธ์์ ํด๋น ์ํ๋ณํ์ ๋ฐ๋ผ ํ์๋กํ๋ ํจ์๋ฅผ ์์ฑํฉ๋๋ค.
ํฌ๊ฒ ๊ด์ฐฐ์(observer) ์ ๊ด์ฐฐ ๋์(entry), ์ต์ (์กฐ๊ฑด) ๊ทธ๋ฆฌ๊ณ ์ฝ๋ฐฑํจ์(๋ก์ง)๊ฐ ์กด์ฌํฉ๋๋ค.
๊ด์ฐฐ์๋ฅผ ์์ฑํฉ๋๋ค.๊ด์ฐฐ ๋์์ ์์ฑํฉ๋๋ค.๊ด์ฐฐ์๋ ๊ด์ฐฐ ๋์์ ๊ด์ฐฐํฉ๋๋ค๊ด์ฐฐ ๋์์ด ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์ํ์ ๋์ด๊ฒ ๋๋ค๋ฉด ์ฝ๋ฐฑ ํจ์๋ฅผ ์คํํฉ๋๋ค.// hooks/useInfiniteScroll.tsx
const useInfiniteScroll = (props: UseInfiniteScrollProps) => {
const { bottom, moreItemList } = props;
const [page, setPage] = useState(1);
useEffect(() => {
const options = {
rootMargin: '30px',
threshold: 1
};
// ๊ด์ฐฐ์๋ฅผ ์์ฑ
const observer = new IntersectionObserver((entries) => {
const target = entries[0];
if (target.isIntersecting) {
setPage((prev) => prev + 1);
}
}, options);
observer.observe(bottom.current);
}, []);
export default useInfiniteScroll;
// components/featured/ItemList.tsx
import React, { useState, useRef } from 'react';
import useInfiniteScroll from 'hooks/useInfiniteScroll';
// ๊ด์ธก ๋์ ์์ฑ
const bottom = useRef(null);
// ๋ฌดํ ์คํฌ๋กค ํ
์ค ์ฌ์ฉ
const page = useInfiniteScroll({ bottom, moreItemList });
// ์์ดํ
๋ชฉ๋ก ์๋ก ๋ถ๋ฌ์ค๊ธฐ
const moreItemList = () => {
const data = {
page: page,
limit: 50
};
const query = querystring.stringify(data);
fetcher
.get(`${ApiPath.featured.featured}/${featuredIdx}?${query}`)
.then((res) => {
const newItemList = res.data.list;
setTotalItemList((totalItemList) => totalItemList.concat(newItemList));
})
.catch((error) => {
console.error(error.message);
});
};
return (
// ๊ด์ธก ๋์ ์ ์ธ
<div>
blah blah...
<div ref={bottom} />
)
Intersection Observer ์ ์กฐ๊ฑด์ผ๋ก ๋ฌด์์ ๋ฃ์ด์ค ๊ฒ์ธ๊ฐ?๊ด์ฐฐ ๋์ ์ ๋ฌด์์ธ๊ฐ?๊ด์ฐฐ ๋์์ ๋ฆฌ์คํธ์ ๋งจ ์๋์ div ํ๊ทธ๋ก ์ ์ธํ๋ค.์ฝ๋ฐฑํจ์) ๋ฅผ ๋ฃ์ด์ค ๊ฒ์ธ๊ฐ?