Image Lazy Loading
먼저, 단어 그대로 보자면 사진을 불러오는 행위를 지연시킨다는 말이다.
웹페이지에 사진이 많은 경우에는 눈에 보여지지 않는 부분까지 불러올 필요는 없다.
실제로 눈에 보이는 부분을 먼저 불러오고, 보이지 않는 부분은 나중에 불러오는 방식이다.
긴 사진 목록을 가진 특정 웹사이트에 접속했을 때, 스크롤을 내리면서 아래 사진을 차례로 불러오는 상황을 예시로 들 수 있겠다.
이미지 요청을 최소화하여 초기 로딩시간을 단축할 수 있고, 네트워크 통신도 줄일 수 있다.
구현 방법
단순히 <img /> 태그의 속성을 사용하는 방법부터, Javascript event, Web API를 사용하는 방법까지 다양한 방법이 있다.
난 그 중에 Web API를 사용하는 방법으로 구현해봤다.
Intersection Observer API
MDN에 따르면, 해당 API는 특정 element와 viewport가 교차하는 변화를 비동기로 관찰하는 API이다.
쉽게 말하자면, 화면에 보이고 있는지 아닌지를 알려준다는 말이다.
화면에 보이는지 아닌지는 Element.getBoundingClientRect()로도 충분히 알 수 있지만,
브라우저 reflow를 발생시키기 때문에 좋은 선택은 아니다.
생성법
Intersection observer는 관찰하는 element의 가시성이 변경될 때 실행할 callback과 간단한 options를 parameter로 받는다.
때문에 이에 필요한 값들을 설정해서 넣어주어야 한다.
const let observer = new IntersectionObserver(callback, options);
callback(entries, observer)
- entries
- 가시성이 변경된 element를 나타내는 IntersectionObserverEntry 객체의 배열
- observer
- 해당 callback을 호출한 IntersectionObserver
options [optional]
- root
- element의 가시성을 확인할 root element.
- 조상 element 혹은 Document가 해당된다.
- rootMargin
- 교차 계산에 대해 root의 바운딩 박스에 적용할 오프셋을 나타내는 문자열.
- 교차 계산 시에 root의 크기를 키우거나 줄일 때 사용할 수 있다.
- 기본값은 "0px 0px 0px 0px"
- threshold
- 타겟 element의 가시성 정도를 나타내는 숫자 혹은 배열
- 범위는 0 ~ 1
- 기본값은 0
사용법
import { useRef, useEffect, useCallback } from 'react';
type IntersectHandler = (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void;
const useIntersection = (onIntersect: IntersectHandler, options?: IntersectionObserverInit) => {
const ref = useRef(null);
const callback = useCallback(
(entries: IntersectionObserverEntry[], observer: IntersectionObserver) =>
entries.forEach((entry) => entry.isIntersecting && onIntersect(entry, observer)),
[onIntersect],
);
useEffect(() => {
if (!ref.current) return;
const observer = new IntersectionObserver(callback, options);
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref, options, callback]);
return ref;
};
export default useIntersection;
구현하기
위 hook을 사용해서 <img /> 태그를 관찰 대상으로 지정해주면 된다.
lazy loading의 작동 순서는 아래와 같다.
- data-src 에 image url 지정
- 교차되는 순간에 data-src 제거 및 src에 image url 지정
- 관찰 중지
처음에 data-src에 image url을 넣는 이유는, 실제로 html에서 인식을 하지 못하기 때문이다.
인식을 못하도록 <img /> 태그를 만든 후에, 교차가 되면 인식이 가능한 src로 image url을 바꿔주는 방법이다.
이 작업들을 따로하는 image component로 분리 후에 사용하면 편리하다.
const LazyImage = ({ src, alt }: { src: string; alt: string }) => {
const [loaded, setLoaded] = useState(false);
const ref = useIntersection(({ target }, observer) => {
if (loaded) return;
const imageSrc = target.getAttribute('data-src') as string;
target.removeAttribute('data-src');
target.setAttribute('src', imageSrc);
observer.unobserve(target);
setLoaded(true);
});
return <img alt={alt} data-src={src} ref={ref} />;
};
export default LazyImage;
/*
callback
({ target }, observer) => {
if (loaded) return;
const imageSrc = target.getAttribute('data-src') as string;
target.removeAttribute('data-src');
target.setAttribute('src', imageSrc);
observer.unobserve(target);
setLoaded(true);
}
*/
options는 따로 추가하지 않고 기본 값으로만 되도록 했다.
구현예시
function List() {
return (
<ProductList>
{data.map(({ id,imageUrl }) => (
<Item key={id}>
<LazyImage src={imageUrl} alt="product" />
</Item>
))}
</ProductList>
);
}
export default List;
적용이 잘 되었다면 이처럼 사진을 나눠서 불러오게 된다 !
'개발' 카테고리의 다른 글
Next - Image (nextjs 13) (0) | 2023.03.07 |
---|---|
React - Pagination 구현하기 (with. custom hook) (0) | 2023.03.02 |
npm - Custom hook Publish ! (0) | 2023.02.08 |
React - Effect Hook ( Clean-up ) (0) | 2023.01.27 |