React & Webpack 이미지 로딩 최적화
이 글에서는 responsive-loader를 적용하는 법을 중심적으로 다룬다. 원리에 대한 내용은 '정적 이미지를 로딩하는 가장 완벽한 방법' 포스팅에 정리했다.
1. responsive-loader
자바스크립트로 만들어진 웹팩 플러그인이다. npm 패키지로 배포되어 바로 설치해 사용할 수 있다.
npm i responsive-loader
2. 사용법
2.1. 웹팩 적용
아래와 같이 설정하면 프로젝트 코드 내에서 import하는 모든 png, jpg 파일에 대해서 로더가 동작한다. 로더에 따라 import하는 데이터의 형식이 달라지는 것을 유의하자.
module.exports = {
// ...
module: {
rules: [
{
test: /\.(png|jpe?g)$/,
use: [
{
loader: 'responsive-loader',
options: {
sizes: [320, 640, 960, 1200, 1800, 2400], // 열거한 사이즈들에 대한 이미지들을 만든다.
},
},
],
type: 'javascript/auto',
},
],
},
}
2.2. 타입 선언
import imgSrc from './images/example.png'
가장 기본적인 file-loader 등을 사용하면, imgSrc변수에 바로 string타입의 url 주소가 할당된다. 하지만 responsive-loader는 다양한 기능을 제공하므로 더 복잡한 타입을 반환한다. 따라서 타입스크립트를 사용할 경우, 다음과 같이 타입선언이 필요하다
interface ResponsiveImageOutput {
src: string
srcSet: string
placeholder: string | undefined // data URL
images: { path: string; width: number; height: number }[]
width: number
height: number
toString: () => string
}
declare module '*.jpg' {
const src: ResponsiveImageOutput
export default src
}
declare module '*.png' {
const src: ResponsiveImageOutput
export default src
}
2.3. 기본 사용
아래와 같이 사용할 수 있다.
import responsiveImage from 'img/myImage.png?format=jpg';
import responsiveImageWebp from 'img/myImage.jpg?format=webp';
...
<picture>
<source srcSet={responsiveImageWebp.srcSet} type='image/webp' sizes='(min-width: 1024px) 1024px, 100vw'/>
<img
src={responsiveImage.src}
srcSet={responsiveImage.srcSet}
width={responsiveImage.width}
height={responsiveImage.height}
sizes='(min-width: 1024px) 1024px, 100vw'
loading="lazy"
/>
</picture>
실제로 빌드를 해보면 아래 그림처럼 하나의 이미지에 대해서
- webpack에 설정한 너비 폭에 대응하는 파일을 만들고
- 사용한 포맷(jpg, webp) 각각에 대한 이미지를 생성한다.
3. 커스텀 컴포넌트
정적 이미지는 프로젝트 전체에서 자주 사용될 것이므로, 하나의 독립적인 컴포넌트로 만들 수 있다.
3.1. 소스 코드
import React, { useRef } from 'react'
import './StaticImage.scss'
interface Props {
webpSrc: ResponsiveImageOutput
imgSrc: ResponsiveImageOutput
alt: string
width?: string
height?: string
sizes?: string
loading?: 'eager' | 'lazy'
onLoad?: () => void
}
const StaticImage: React.FC<Props> = ({
webpSrc,
imgSrc,
alt,
width = '100%',
height = '100%',
sizes = '(max-width: 1200px) 100vw, 1201px',
loading = 'eager',
onLoad,
}) => {
const picRef = useRef<HTMLPictureElement>(null)
// 이미지가 로드되면 클래스를 추가해서 CSS 변경시켰다.
const handleLoad = function staticImageLoaded() {
const picture = picRef.current!
picture.classList.add('loaded')
onLoad && onLoad()
}
return (
<picture
ref={picRef}
className="static-image"
style={{
backgroundImage:
'url("' +
(imgSrc.placeholder ? imgSrc.placeholder : DEV_BLUR_DATA) +
'")',
}}
>
<source srcSet={webpSrc.srcSet} type="image/webp" sizes={sizes} />
<img
alt={alt}
src={imgSrc.src}
srcSet={imgSrc.srcSet}
sizes={sizes}
loading={loading}
onLoad={handleLoad}
style={{ width, height }}
/>
</picture>
)
}
export default StaticImage
.static-image {
background-repeat: no-repeat;
background-size: cover;
img {
display: block;
object-fit: cover;
opacity: 0;
transition: opacity 250ms ease-in-out;
}
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
background-color: white;
animation: static-image-pulse 1500ms infinite;
}
}
@keyframes static-image-pulse {
0% {
opacity: 0;
}
50% {
opacity: 0.5;
}
100% {
opacity: 0;
}
}
.static-image.loaded {
img {
opacity: 1;
}
&::before {
animation: none;
content: none;
}
}
3.2. 사용 예시
아래처럼 손쉽게 사용할 수 있다.
import React from 'react'
import StaticImage from '@/atoms/ui/StaticImage'
import responsiveImage from '@/assets/images/home-section-2/narrow-1.jpg?format=jpg'
import responsiveImageWebp from '@/assets/images/home-section-2/narrow-1.jpg?format=webp'
function Guide() {
return (
<div>
<StaticImage
webpSrc={responsiveImageWebp}
imgSrc={responsiveImage}
alt="햇살이 드는 카페 의자"
width="40vw"
height="30vw"
/>
</div>
)
}

5. 참고 자료
이전 포스트
Redux Toolkit 기본 사용법
다음 포스트