이미지가 노출되는 웹페이지의 로딩 속도를 빠르게 해서 UX 향상 및 고객이탈률 감소를 위해 필요합니다. 웹페이지 로딩 속도 개선을 위해 웹컨텐츠 중 대표적으로 무거운 컨텐츠에 이미지, 비디오, 폰트가 있는데, 이 중 이미지 최적화는 프론트엔드에서 할 수 있는 웹 성능 개선 방법 중 기본중의 기본입니다.
이미지 저장 위치가 직접 컨트롤할 수 있는 서버나 로컬 디렉터리라면 직접 이미지 사이즈를 줄이는 툴을 이용합니다. (추천 Tool: https://squoosh.app/)
이미지의 width, height는 화면에 노출될 max width, max height의 2배 정도면 적절합니다. 이유는 최신 모니터 및 스크린들이 고밀도 고화질을 지원하는 Retina Display 기술을 사용하고 있기 때문에 실제 화면에 보이는 사이즈보다 더 큰 이미지를 불러와서 본 사이즈보다 작게 노출해야 이미지 품질 저하를 느끼기 어렵기 때문입니다. (결국 UX 차원)
이미지 포맷은 svg 또는 webp를 사용합니다. 소형 이미지 또는 로고를 다룰 때 주로 쓰는 벡터 이미지 포맷인 svg외에 비트맵 이미지인 png, jpg/jpeg를 주로 사용하는데, 구글에서 개발한 webp 포맷을 사용하면 jpg보다도 가볍고 png 만큼이나 좋은 화질을 가진 이미지를 사용할 수 있습니다.
단, webp와 같은 최신 이미지 포맷을 지원하지 않는 브라우저가 있을 수 있으니 이미지 로딩 시 아래와 같은 패턴이 권장됩니다. (크로스 브라우징 고려)
<picture>
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="photo">
</picture>
// picture tag는 children tag들 중 하나의 tag만 보여줍니다.
// 브라우저가 webp 이미지포맷을 지원하면 source tag의 webp 이미지를 보여주고
// 브라우저가 webp 이미지포맷을 지원하지 않으면 img tag의 jpg 이미지를 보여줍니다.
하나의 페이지에 컨텐츠가 많아 랜딩 화면에 보이지 않는 스크롤다운 해야만 볼 수 있는 이미지들이 많을 때 주로 사용합니다.
intersection observer를 사용해서 스크롤 다운해서 이미지가 보이기 직전에 로딩해서 페이지 랜딩 시 빠른 속도를 느끼게 해줍니다.
useEffect(() => {
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
};
const options = {};
const observer = new IntersectionObserver(callback, options);
observer.observe(imgRef1.current);
observer.observe(imgRef2.current);
observer.observe(imgRef3.current);
}, []);
return (
...
<img data-src={image_url} ref={imgRef1} />
...
);
// useRef로 랜딩 화면 아래에 감춰진 이미지들을 각각 지정해줍니다.
// componentDidMount 시 해당 이미지들은 intersection observer의 observe란 메소드를 통해 화면에 ref로 지정한 element가 보이는 지를 계속 감시합니다.
// 스크롤다운을 통해 element가 로딩될 시점이 되면 img.src 에 해당 ref로 지정한 img tag의 data-src로 넣어둔 이미지 url을 할당함으로써 이미지 로딩을 수행합니다.
// img tag의 src 속성에 이미지 url을 할당하는 그 순간 이미지를 로드합니다. 그래서 lazy loading을 위해 data-src에 잠시 이미지 url을 담아둡니다.
...
import LazyLoad from 'react-lazyload';
...
return (
...
<LazyLoad once offset={200}>
<img src={image_url} />
</LazyLoad>
...
);
// LazyLoad로 nested 된 단일 엘리먼트/컴포넌트는 viewport에 들어왔을 때 이미지 로딩됩니다.
// once 라는 속성을 true로 해주면 observe하던 것을 로딩 후 unobserve 시킵니다.
// offset 속성에 숫자를 넣어주면 해당 엘리먼트의 top 위치보다 숫자 px 만큼 높은 위치에서 미리 로딩을 수행합니다. intersection observer에도 다 있는 기능이지만 보다 추상화되어 쉽게 구현이 가능합니다.
useEffect(() => {
React.lazy(import('./component/ImgModal')); // 이미지 모달 로딩
const img = new Image();
img.src = firstImgUrl;
}, []);
// componentDidMount 시점에 이미지 모달을 로딩 후
// 이미지 모달이 띄워졌을 때 보여질 첫 이미지만 preload 해서
// 이미지 캐싱이 안되었을 때도 빠르게 화면에 이미지가 보여짐