React Lazy Load with Intersection Observer API
Simple demo to learn Intersection Observer API
by cadenzah
HTML
<html>
<body>
</body>
</html>
CSS
.card-grid {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 350px;
border: 1px solid black;
overflow: auto;
}
.card {
display: flex;
align-items: center;
background-color: skyblue;
width: 50%;
height: 100px;
min-height: 100px;
margin: 20px;
}
JavaScript
const { useEffect, useState } = React;
import { getProducts } from '../../api';
import Product from './product';
const Products = () => {
const [products, setProducts] = useState([]);
// 요 훅이 가장 먼저 실행된다
const [observer, setElements, entries] = useIO({
threshold: 0.25,
rootMargin: '30px',
root: null,
});
// 가장 처음, categoryId를 기반으로 상품 "전체" 목록 로드 (데이터)
useEffect(() => {
const fetchData = async () => {
const result = await getProducts(categoryId);
setProducts(result.data.payload);
};
fetchData();
}, [categoryId]);
// 'lazy' 클래스를 가진 이미지 요소들을 참조 초기화 (뷰)
// 각 뷰 요소는 존재하며, "이미지 파일 로드만 안 이루어진 상태"
useEffect(() => {
if (products.length) {
let img = Array.from(document.getElementsByClassName('lazy'));
setElements(img);
}
}, [products, setElements]);
// 현재 뷰포트 내에 존재하는 요소에 대하여 이미지 로드 처리
useEffect(() => {
entries.forEach(entry => {
// 최초 실행시, 바로 뷰포트에 보이는 요소들은 바로 로딩 시작
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove("lazy");
// 원래 최초에는 모든 요소가 observe 되어있는 상태
observer.unobserve(lazyImage); // 관련 attr 갱신이 완료되면 해당 요소 unobserve
}
});
}, [entries, observer]);
return (<div></div>);
}
const useIO = (options) => {
// 뷰 요소들
const [elements, setElements] = useState([]);
// observer가 감시하고 있는 요소 대상
// - IntersectionObserverElement로, DOMElement와 다름
const [entries, setEntries] = useState([]);
const observer = useRef(null);
const { root, rootMargin, threshold } = options || {};
useEffect(() => {
// 최초에는 elements가 []이므로 실행 안댐
// 두번째 이후에서는 elements에는 "모든" 이미지 요소가 들어있다
if (elements.length) {
console.log('OBSERVER CONNECTING');
// (1) 새 observer를 선언하고
observer.current = new IntersectionObserver((ioEntries) => {
setEntries(ioEntries);
}, {
threshold, root, rootMargin,
});
// (2)...