개발 업무를 진행하면서 서비스의 웹 성능 최적화를 위해 사용될 이미지를 관리자에서 압축을 하고 CDN에 업로드할 필요가 있었다. (서비스의 특성상 이미지가 많이 사용된다.)
어떻게 적용할 수 있는 지 알아보자.
유저가 이미지를 업로드를 했을 때 해당 이미지를 압축하고 해당 이미지를 CDN 서버에 업로드해야 된다.
일단 클라이언트 단에서 이미지를 받고 압축을 하기 위한 방법으로 찾아본 결과 다음 2가지 방법이 있었다.
각각 하나 씩 적용해보자.
코드로 살펴보자.
"use client"
import { useState } from 'react';
export default function WebpComponent() {
const [originalImage, setOriginalImage] = useState(null);
const [compressedImage, setCompressedImage] = useState(null);
const [quality, setQuality] = useState(0.8); // 초기 퀄리티 설정 (0.8 = 80%)
const handleImageUpload = (event) => {
const imageFile = event.target.files[0];
setOriginalImage(URL.createObjectURL(imageFile));
const reader = new FileReader();
reader.readAsDataURL(imageFile);
reader.onloadend = () => {
const image = new Image();
image.src = reader.result;
image.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 원하는 크기로 캔버스 크기 설정 (여기서는 원본 크기)
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
// WebP 형식으로 변환 및 압축
const compressedDataUrl = canvas.toDataURL('image/webp', quality);
setCompressedImage(compressedDataUrl);
};
};
};
const handleQualityChange = (event) => {
setQuality(Number(event.target.value));
};
return (
<div>
<h1>Image Upload and Compression</h1>
<input type="file" accept="image/*" onChange={handleImageUpload} />
<div style={{ margin: '20px 0' }}>
<label>
Quality:
<input
type="number"
min="0.1"
max="1"
step="0.1"
value={quality}
onChange={handleQualityChange}
/>
</label>
</div>
<div style={{ margin: '20px 0' }}>
{originalImage && (
<div>
<h2>Original Image</h2>
<img src={originalImage} alt="Original" style={{ maxWidth: '100%' }} />
</div>
)}
{compressedImage && (
<div>
<h2>Compressed Image (WebP)</h2>
<img src={compressedImage} alt="Compressed" style={{ maxWidth: '100%' }} />
</div>
)}
</div>
</div>
);
}
사용자의 이미지를 입력받고 해당 이미지를 webp로 압축/변환하고 화면에 보여주도록 코드를 구성했다.
canvas에 이미지를 다시 그리는 방식으로 압축을 진행하는 것이다. 과정은 다음과 같다.
를 통해
<canvas> 요소를 생성 생성하고 2D 그래픽 컨텍스트를 가져온다. image/jpeg 또는 image/webp의 퀄리티를 지정할 수 있는 encoderOptions 값을 0.7로 설정했다. 
toBlob을 통해 이미지 데이터를 Blob 객체로 변환하여 추후 CDN에 업로드할 때 사용할 수 있을 것이다. (비동기적으로 동작한다.
결과를 확인해보자. jpg 파일을 업로드 했을 때 절반 가량 이미지 용량이 줄어든 것을 확인할 수 있었다.
압축 전

압축 후

모듈을 사용하면 더 간단하게 이미지를 압축하고 webp로 변환할 수 있다. (용량 참고)
코드는 다음과 같다.
"use client"
import { useState } from 'react';
import imageCompression from 'browser-image-compression';
export default function ImageUpload() {
const [originalImage, setOriginalImage] = useState(null);
const [compressedImage, setCompressedImage] = useState(null);
const handleImageUpload = async (event) => {
const imageFile = event.target.files[0];
setOriginalImage(URL.createObjectURL(imageFile));
const options = {
maxSizeMB: 10,
useWebWorker: true,
initialQuality: 0.7,
fileType: 'image/webp'
};
try {
const compressedFile = await imageCompression(imageFile, options);
const compressedImageUrl = URL.createObjectURL(compressedFile);
setCompressedImage(compressedImageUrl);
} catch (error) {
console.error('Error compressing the image:', error);
}
};
return (
<div>
<h1>Image Upload and Compression</h1>
<input type="file" accept="image/*" onChange={handleImageUpload} />
<div style={{ margin: '20px 0' }}>
{originalImage && (
<div>
<h2>Original Image</h2>
<img src={originalImage} alt="Original" style={{ maxWidth: '100%' }} />
</div>
)}
{compressedImage && (
<div>
<h2>Compressed Image</h2>
<img src={compressedImage} alt="Compressed" style={{ maxWidth: '100%' }} />
</div>
)}
</div>
</div>
);
}
위 코드를 기반으로 이미지 압축/변환 과정은 다음과 같다.
이미지 파일 객체를 들고 온다.
option 객체를 정의하고 내부 설정을 한다.
browser-image-compression에서 import한 imageCompression 메서드에 imageFile, options을 넘긴다. (옵션 참고)
반환된 결과값을 URL.createObjectURL 메서드를 통해 임시 URL을 생성하여 화면에 보여준다.

createObjectURL- 메모리에 있는 객체(일반적으로 Blob이나 File 객체)에 대한 임시 URL을 생성 (참고)압축 전

압축 후

직접 구현한 것이랑 차이가 없는 것을 확인할 수 있다. 즉 동일한 로직을 통해 압축을 적용했다는 것을 예상할 수 있는데 모듈 내부 코드를 확인해보자.
https://github.com/Donaldcwl/browser-image-compression/blob/master/lib/utils.js#L256
browser-image-compression 모듈과 비교했을 때 5분의 1수준의 용량을 가지고 있어 가볍다. (참고)
코드를 통해 살펴보자.
'use client'
import React, { useState } from 'react';
import Compressor from 'compressorjs';
const CompressorComponent = () => {
const [compressedImage, setCompressedImage] = useState(null);
const [originalImage, setOriginalImage] = useState(null);
const handleImageUpload = (event) => {
const file = event.target.files[0];
if (file) {
setOriginalImage(URL.createObjectURL(file));
new Compressor(file, {
quality: 0.7, // 압축 품질 설정 (0 ~ 1)
mimeType: "image/webp",
success: (compressedResult) => {
setCompressedImage(URL.createObjectURL(compressedResult));
},
error(err) {
console.error(err.message);
},
});
}
};
return (
<div>
<h1>Image Compressor</h1>
<input type="file" accept="image/*" onChange={handleImageUpload} />
{originalImage && (
<div>
<h2>Original Image</h2>
<img src={originalImage} alt="Original" style={{ maxWidth: '100%', margin: '10px 0' }} />
</div>
)}
{compressedImage && (
<div>
<h2>Compressed Image (WebP)</h2>
<img src={compressedImage} alt="Compressed" style={{ maxWidth: '100%', margin: '10px 0' }} />
</div>
)}
</div>
);
};
export default CompressorComponent;
이미지 압축/변환 과정은 browser-image-compression를 사용했을 경우와 역시나 비슷하다.
이미지 파일 객체를 들고 온다.
import한 Compressor(class이다)
를 이용해 new Compressor로 인스턴스를 생성한다. 인스턴스를 생성할 때 이미지 파일과 옵션값을 넘겨준다. (옵션 참고)

success fallback 함수를 통해 압축된 결과값을 받고 createObjectURL를 통해 임시 URL를 생성해서 화면에 노출한다. (비동기로 압축 진행)
결과를 보면 앞서 본 2개의 방식과 차이가 없다.
압축 전

압축 후

이미지를 모두 webp로 변환을 했는데 브라우저 호환성을 살펴보자. 서비스는 chrome 59에서도 동작을 해야한다.
따라서 이미지 포맷의 호환성이 중요한데, 거의 모든 환경에서 지원되지만 ios 14미만에서는 안되는 것을 확인할 수 있다. 서비스는 ios 14이상부터 지원되기 때문에 이슈가 없다.
https://caniuse.com/?search=webp

이미지를 사용할 때뿐만 아니라 외부 패키지를 사용할 때도 브라우저 호환성을 꼭 확인하자.
webp란 구글에서 개발한 이미지 파일 포맷이다. JPEG와 PNG 포맷보다 더 높은 압축 효율을 제공하며, 무손실 및 유손실 압축을 모두 지원한다. 이를 통해 웹페이지 로딩 속도를 향상시키고 저장 공간을 절약할 수 있다.
대표적으로 JPG/JPEG와 PNG에 대해 손실/무손실 압축 지원 여부를 보자.
JPG/JPEG
PNG
canvas란 HTML5의 요소 중 하나로, 자바스크립트를 사용하여 동적으로 그래픽을 그리기 위한 도구이다. 픽셀 단위의 그림을 그릴 수 있는 2D 그래픽 컨텍스트와 3D 그래픽을 그릴 수 있는 WebGL 컨텍스트를 제공한다. 주로 게임, 데이터 시각화, 애니메이션 등에 사용된다.
blob이란 Binary Large Object의 약자로, 대용량의 이진 데이터를 저장하기 위한 데이터 타입이다. 파일, 이미지, 비디오 등의 바이너리 데이터를 다룰 때 사용된다. JavaScript에서는 Blob 객체를 사용하여 파일이나 데이터를 로컬에 저장하거나 서버로 전송할 수 있다.
참고내역
https://youngble.tistory.com/73
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
https://www.jeong-min.com/40-image-upload-preview/