일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- 리버스 프록시
- 이미지 최적화
- 렌더링 과정
- webp
- 이미지 포맷 변경
- nextJS
- aws
- 무중단
- tcp
- 자동화
- 3-Way HandShake
- nginx
- https
- 검색엔진최적화
- 배포
- SSL
- CI
- workflow
- 이미지 압축
- certbot
- 브라우저
- 로드 밸런싱
- ec2
- DNS
- 인증서
- pm2
- 성능 개선
- gitgub actions
- TLS
- SSH
- Today
- Total
개발일기
이미지 최적화하기(WebP, browser-image-compression) 본문
📋 개요
웹에서 이미지는 사용자 경험과 페이지 성능에 직접적인 영향을 준다. 프로젝트를 진행해보면서 웹사이트 로딩 시간에 가장 영향을 준건 역시 이미지이다. 구글 리서치 자료에 따르면 사이트의 로딩 시간이 3초 이상일 때 32%, 5초 이상은 90%, 6초 이상은 106% 마지막으로 10초가 넘으면 123%의 이탈률이 발생한다고 한다. 이는 곧 검색엔진 최적화(SEO)와 수익에도 악영향을 끼친다.
웹사이트의 성능 개선하기 위해 실제 프로젝트에 적용할 수 있는 이미지 최적화 방법들을 알아보자.
1️⃣ 이미지 포맷 변경 (JPG, PNG → WebP)
🖼️ WebP란?
WebP 포맷은 인터넷에서 흔히 쓰이는 GIF, JPG, PNG 포맷을 대체하기 위해 개발되었습니다. WebP의 가장 큰 장점은, 이미지 품질이 같을 때 WebP 파일의 크기가 다른 포맷의 파일에 비해 훨씬 작다는 것입니다. 예를 들어, WEbP 파일의 크기는 같은 내용의 JPG, PNG 파일보다 20% ~ 30% 작습니다.
특히 애니메이션 이미지의 경우, GIF 파일을 WebP 파일로 바꾸면 이미지 품질은 유지하면서 파일 크기는 절반 이하로 줄일 수 있습니다.
가끔 그럴 때가 있었다. 파일 이름의 확장자만 변경하면, 강제로 그 확장자로 실행되곤 했다.
⚠️ 그렇다면 이것도 확장자만 WebP로 변경하면 되지 않을까?
new File()을 사용해서 jpeg인 파일을 확장자만 변경해서 확장자가 webp인 파일을 하나 만들었다.
const convertToWebpFile = new File([file], `${file.name.split(".")[0]}.webp`, {
type: "image/webp",
});
하지만 확장자만 변경하는 것만으로는 이미지 포맷이 변하지 않기 때문에 이미지 사이즈가 변경되지 않는다.
단순하게 파일 이름의 확장자만 변경하는 것이기 때문에 이미지의 실제 내용은 그대로 유지되고,
파일 시스템에서는 확장자만 바뀐 것처럼 보일 뿐이다.
아래는 확장자 이름만 변경했을 때의 결과이다.
type은 webp로 변경 되었지만 이미지의 size는 그대로이다.
그러면 어떤 방법으로 이미지 포맷까지 변경할수 있을까?
코드로 확인해 보자.
export const convertImageToWebp = (file: File): Promise<File> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = URL.createObjectURL(file); // file을 url로 변환
img.onload = () => { // 이미지가 로드된 후 실행할 코드
const canvas = document.createElement("canvas"); // <canvas> 생성
canvas.width = img.width; // 캔버스 width 설정
canvas.height = img.height; // 캔버스 height 설정
const ctx = canvas.getContext("2d"); // 2D 그래픽 컨텍스트 생성(도화지)
ctx?.drawImage(img, 0, 0); // 도화지에 이미지 그리기(0의 값은 도화지에 이미지를 꽉 채워서 그림)
canvas.toBlob((blob) => {
if (blob) {
const webpFile = new File([blob], `${file.name.split(".")[0]}.webp`, {
type: "image/webp",
lastModified: Date.now(),
}); // blob을 새로운 webp 확장자의 File로 생성
resolve(webpFile); // webp 확장자의 File 반환
} else {
reject(new Error("Image conversion failed")); // 실패시 에러처리
}
}, "image/webp");
};
img.onerror = () => reject(new Error("Image load failed")); // 이미지 로드 실패시
});
};
convertImageToWebp 함수는 File 객체를 받아 webp 포맷의 새로운 File 객체로 변환하여 반환한다.
함수의 흐름은 아래와 같다.
① 먼저 전달받은 File을 이미지로 로드한다. (<img> 요소)
② 이미지를 <canvas>에 그린다.
③ <canvas>의 내용을 canvas.toBlob()을 통해 Blob 형식으로 추출한다.
➃ 이 Blob을 기반으로 새롭게 webp 확장자의 File 객체를 생성해 반환한다.
Promise를 사용하는 이유는 canvas.toBlob()라는 함수가 콜백 기반의 비동기 함수이기 때문이다.
canvas.toBlob()에서만 blob 값을 다룰 수 있으므로, 콜백 밖으로 값을 꺼내 쓰려면 Promise로 감싸야 한다.
파일을 보면 확장자 변경과 함께 File size가 줄어든 것을 확인할 수 있다.
📌 네트워크 전송 크기 비교(KB)
WebP로 변환한 후, 네트워크 전송 크기는 변경 전 306KB에서 133KB로 약 56% 감소했다.
👎 WebP 포맷으로 변경 전
👍 WebP 포맷으로 변경 후
2️⃣ 이미지 압축
두번째 방법으론 이미지 압축 방법이다.
나는 이미지 파일을 압축할 수 있는 라이브러리인 browser-image-compression을 사용해 이미지 최적화를 했다.
🛠 설치
npm install browser-image-comporession
📦 기본 사용법
import imageCompression from "browser-image-compression";
const options = {
maxSizeMB: 0.6,
maxWidthOrHeight: 1280,
useWebWorker: true,
};
const compressImage = async (file: File) => {
try {
const compressedFile = await imageCompression(file, options);
return compressedFile;
} catch (error) {
console.error("압축 실패:", error);
}
};
🧩 주요 옵션 설명
옵션 이름 | 타입 | 설명 |
maxSizeMB | number | 최종 이미지의 최대 용량(MB)을 설정합니다. 예: 0.5는 0.5MB (약 500KB) 이하로 압축합니다. |
maxWidthOrHeight | number | 이미지의 가로 또는 세로 중 가장 긴 변의 최대 길이를 지정합니다. 예: 1280이면 너비 또는 높이 중 더 큰 쪽이 1280px로 리사이징 됩니다. |
useWebWorker | boolean | Web Worker를 사용해 압축 작업을 백그라운드에서 수행할지 여부입니다. true 권장 백그라운드에서 스레드를 실행해 UI가 끊김없이 동작됩니다. |
💻 실제로 적용하기
나는 여러 곳에서 이미지를 올리기 때문에 사이즈별로 다르게 용량을 줄이면 좋을 것 같아서 다음과 같이 코드를 변경했다.
imageCompressionFn 함수는 file과 size를 매개변수로 받고, size(small,medium,large)에 따라 옵션에 맞게 용량을 줄인다.
import imageCompression from "browser-image-compression";
import { convertImageToWebp } from "./convertImageToWebp";
const options: Record<
string,
{ maxSizeMB: number; maxWidthOrHeight: number; useWebWorker: boolean }
> = {
small: { maxSizeMB: 0.1, maxWidthOrHeight: 300, useWebWorker: true },
medium: { maxSizeMB: 0.5, maxWidthOrHeight: 720, useWebWorker: true },
large: { maxSizeMB: 1, maxWidthOrHeight: 1280, useWebWorker: true },
};
const getCompressionOptions = (size: string) => {
return options[size];
};
export const imageCompressionFn = async (file: File, size: string) => {
const options = getCompressionOptions(size);
const convertedToWebp = await convertImageToWebp(file); // webp 확장자로 변경하는 함수
const compressionFile = await imageCompression(convertedToWebp, options);
return compressionFile;
};
📌 네트워크 전송 크기 비교(KB)
이미지를 압축한 후, 네트워크 전송 크기는 변경 전 306KB에서 93KB로 약 70% 감소했다.
👎 이미지 압축 전
👍 이미지 압축 후
📌 네트워크 전송 크기 최종 비교(KB)
항목 | 변환 전 크기 | 변환 후 크기 | 감소율 |
✅ JPEG → WebP | 306KB | 133KB | 💡 56% 감소 |
✅ 압축 전 → 압축 후 | 306KB | 93KB | 💡 70% 감소 |
✅ JPEG 압축 전 → WebP 압축 후 | 306KB | 83KB | 💡 73% 감소 |
'SideProject(My-Selectshop-Finder)' 카테고리의 다른 글
NextAuth 로그인 실패시 에러 처리하기 (0) | 2025.03.12 |
---|---|
cloudinary에서 CldUploadWidget 없이 이미지 저장하기 (0) | 2025.02.03 |
NextAuth 사용자 정보 update 및 mutate 하기 (0) | 2025.02.03 |
카카오 지도 API에서 pagination과 필터링: 데이터 정렬 문제 해결하기 (0) | 2024.12.24 |
카카오 지도 api에서 pagination을 통한 45개 데이터 가져오기 (1) | 2024.12.24 |