Skip to content

Commit c933d76

Browse files
committed
응~ 이미지 키워봐~ 최적화 하면 그만이야 (pt1. 이미지 포맷)
1 parent f00f2ad commit c933d76

File tree

8 files changed

+321
-0
lines changed

8 files changed

+321
-0
lines changed

pages/posts/Web/image-format.md

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
---
2+
title: 응~ 이미지 키워봐~ 최적화 하면 그만이야 (pt1. 이미지 포맷)
3+
createdAt: 2024-12-03
4+
category: Web
5+
description: almanac.httparchive.org 의 2022년 보고서에 따르면, 이미지가 하나 이상 포함된 페이지는 99.9%에 달하며, LCP 에 가장 큰 영향을 미치는 요소이기도 합니다. 2024 경북대학교 디자인학과 졸업전시 사이트를 개발하며 경험한 이미지 포맷의 종류와 최적화 방법에 대해 알아보겠습니다
6+
---
7+
8+
# 🤡 응~ 이미지 키워봐~ 최적화 하면 그만이야 (pt1. 이미지 포맷)
9+
10+
:::warning
11+
아직 작성중이거나 검토중인 글입니다. 내용이 부정확하거나 변경될 수 있습니다
12+
:::
13+
14+
<br/>
15+
16+
## 들어가며
17+
18+
[2024년 경북대학교 디자인학과 졸업전시 웹사이트](https://www.knud2024.com/) 개발을 맡게 됐습니다.
19+
전시 웹사이트이기도 하고 디자인학과 학생들의 작품을 소개하는 사이트이기도 하다 보니, 이미지가 미친 듯이 많고 컸습니다
20+
21+
**_"작품 퀄리티는 원본 그대로 보여줘야지!"_** 하는 욕심에
22+
그냥 풀사이즈 이미지를 그대로 때려박았습니다
23+
**_"에이, 요즘 브라우저 성능 좋잖아? 이 정도면 버티겠지~"_** 라고 생각했죠
24+
25+
그리고 그 결과는?
26+
27+
<center>
28+
<img src="./img/image-format/image-1.png" alt="" width="250px"/>
29+
</center>
30+
31+
웬 삐에로가 나타나서 **_"응~ 이미지 그딴식으로 처리해봐~ 브라우저 뻗으면 그만이야"_** 라고 말하는게 아니겠습니까?
32+
33+
결국 실제로 모바일 크롬이 먹통이 되어버렸습니다.
34+
이미지가 가장 중요한 웹사이트가 정작 이미지를 못 보여주는 상황이 되어버린 것이죠
35+
36+
그래서 이번 글에서는 해당 프로젝트에서 삽질한 경험을 바탕으로 이미지 포맷의 종류와 웹사이트에서 이미지 최적화 방법에 대해 알아보겠습니다
37+
38+
<br/>
39+
40+
## 최신 이미지 포맷
41+
42+
웹 성능을 결정짓는 최대 빌런 중 하나는 이미지입니다. <br/>
43+
[almanac.httparchive.org 의 2022년 보고서](https://almanac.httparchive.org/en/2022/page-weight)에 따르면, 이미지가 하나 이상 포함된 페이지는 99.9%에 달하며, LCP 에 가장 큰 영향을 미치는 요소이기도 합니다.
44+
45+
저는 졸업전시 웹사이트에서 이걸 무시했다가 모바일 크롬이 뻗어버리는 참사를 겪었습니다
46+
47+
<center>
48+
<img src="./img/image-format/image-2.png" alt="" width="250px"/>
49+
</center>
50+
51+
이미지 포맷 하나만 잘 골라도, 로딩속도, UX 심지어 Core Web Vitals 점수까지 확 달라집니다
52+
53+
그럼 바로 프론트엔드 개발자라면 반드시 알아야 하는 JPEG (Baseline vs Progressive), WebP, 그리고 최근 주목받는 AVIF 까지 기술적 특성과 렌더링 방식, 성능차이에 대해 알아보겠습니다
54+
55+
<br/>
56+
57+
### 1. JPEG (Joint Photographic Experts Group)
58+
59+
JPEG는 웹에서 수십년간 사골처럼 우려먹은 이미지 포맷입니다.
60+
근데 JPEG 안에서도 **Baseline****Progressive** 두 가지 방식이 있다는 거 아셨나요?
61+
62+
저는 몰랐습니다. 그래서 그냥 기본 옵션인 Baseline 으로 뽑았고, 모바일과 같은 느린 네트워크에서 사이트 열던 사람들은 초딩때 ppt 발표했을때 슬라이드가 한 줄씩 타자기처럼 찍혀나오는 것 마냥 위에서 아래로 한 줄씩 이미지가 로드되었습니다.
63+
64+
두 방식은 **렌더링 방식** 에 차이가 있습니다.
65+
66+
<center>
67+
<figure>
68+
<img src="./img/image-format/image-3.png" alt="https://web.dev/learn/images/jpeg?hl=ko" width="600px"/>
69+
<figcaption>출처 : <a href="https://web.dev/learn/images/jpeg?hl=ko">https://web.dev/learn/images/jpeg?hl=ko</a></figcaption>
70+
</figure>
71+
</center>
72+
73+
<br/>
74+
75+
#### (1) Baseline JPEG
76+
77+
Baseline JPEG 는 이미지가 위에서 아래로 한 줄 (Scanline 단위)씩 순차적으로 렌더링 됩니다.
78+
79+
데이터 전송이 완료된 부분은 완벽한 품질로 표시되지만, 아직 로드되지 않은 하단 부분은 빈 공간으로 남습니다.
80+
81+
느린 네트워크에서는 사용자가 이미지의 윗 부분만 본 채로 나머지 부분이 채워지기를 기다려야하기 때문에 페이지가 깨져 보인다거나 로딩이 오래 걸린다는 인상을 줄 수 있습니다.
82+
83+
<br/>
84+
85+
#### (2) Progressive JPEG
86+
87+
Progressive JPEG 는 이미지를 여러 번의 Scan (Pass) 를 통해 점진적으로 선명하게 렌더링합니다.
88+
89+
첫 번째 스캔에서는 전체 이미지의 저해상도 버전을 표시하고, 이후 데이터가 추가로 전송됨에 따라, 이미지가 점점 더 선명해집니다.
90+
91+
"아 여기 무슨 그림이 있구나" 라는 걸 빠르게 인지할 수 있기 때문에, 사용자는 페이지가 로드되는 동안에도 이미지의 내용을 어느 정도 파악할 수 있습니다
92+
93+
물론 Progressive 는 디코딩할 때 브라우저가 좀 더 많은 CPU 자원을 쓴다는 단점도 있지만, 대부분의 환경에선 이 오버헤드보다 UX 향상 효과가 더 큽니다
94+
95+
<br/>
96+
97+
### 그래서 큰 이미지에 대해서는 Progressive JPEG 쓰라는건 알겠고 어케쓰라는건데
98+
99+
브라우저가 알아서 Progressive JPEG 로 변환해주지는 않고, 직접 이미지를 Progressive JPEG 로 변환해줘야 합니다
100+
101+
보통 이미지 편집 툴에서 JPEG 저장 옵션을 제공하는데, 여기서 Progressive 옵션을 선택해주면 됩니다
102+
103+
근데 너무 귀찮기 때문에 CLI 도구나 빌드, 파이프라인에 통합하는게 좋습니다
104+
105+
<br/>
106+
107+
#### (1) AWS Lambda (Node.js) + Sharp 로 이미지 업로드 시점에 변환하기
108+
109+
![alt text](./img/image-format/image-5.png)
110+
111+
AWS Lambda 에서 S3 트리거 이벤트를 사용하면 이미지 업로드 시점에 자동으로 Progressive JPEG 로 변환할 수 있습니다
112+
([관련 글 : AWS Lambda & S3 트리거로 이미지 최적화 자동화하기](../Aws/lambda-image-optimization.md))
113+
114+
<br/>
115+
116+
#### (2) prebuild 스크립트로 빌드 시점에 변환하기
117+
118+
public 폴더에 이미지가 들어있는 경우, package.json의 prebuild 스크립트를 사용해서 Progressive JPEG 변환 스크립트를 실행할 수 있습니다
119+
120+
```ts
121+
// scripts/jpeg-progressive.ts
122+
import fg from "fast-glob";
123+
import sharp from "sharp";
124+
import fs from "node:fs/promises";
125+
import path from "node:path";
126+
import minimist from "minimist";
127+
128+
const args = minimist(process.argv.slice(2));
129+
const SRC = args.src;
130+
const DEST = args.dest || SRC;
131+
const QUALITY = args.quality || 90;
132+
133+
async function convertToProgressiveJpeg() {
134+
const files = await fg(`${SRC}/**/*.{jpg,jpeg,JPG,JPEG}`, { absolute: true });
135+
136+
for (const file of files) {
137+
const relativePath = path.relative(SRC, file);
138+
const destPath = path.join(DEST, relativePath);
139+
await fs.mkdir(path.dirname(destPath), { recursive: true });
140+
await sharp(file).jpeg({ quality: QUALITY, progressive: true }).toFile(destPath);
141+
142+
console.log(`Converted: ${file} > ${destPath}`);
143+
}
144+
}
145+
146+
convertToProgressiveJpeg().catch(console.error);
147+
```
148+
149+
```json
150+
// package.json
151+
{
152+
"scripts": {
153+
"prebuild": "ts-node scripts/jpeg-progressive.ts --src=public/images --dest=public/images --quality=85"
154+
}
155+
}
156+
```
157+
158+
이렇게 하면 빌드 시점에 public/images 폴더 내의 모든 JPEG 이미지를 Progressive JPEG 로 변환해줍니다
159+
160+
`public/` 안에 있는 이미지들은 그냥 복사되기 때문에 플러그인이 적용되지않아 prebuild 스크립트에서 덮어쓰는 방식으로 처리하면 됩니다
161+
162+
<br/>
163+
164+
#### (3) 번들러(Vite) 플러그인으로 변환하기
165+
166+
Vite 와 같은 번들러의 빌드 파이프라인에서 이미지 최적화를 걸어두면, 코드에 `import` 된 이미지도 Progressive JPEG 로 변환할 수 있습니다
167+
168+
저는 `unplugin-imagemin/vite`를 사용했는데, 다른 다양한 플러그인도 있으니 취향껏 골라쓰면 됩니다
169+
170+
```ts
171+
// vite.config.ts
172+
import { defineConfig } from "vite";
173+
import ImageMin from "unplugin-imagemin/vite";
174+
175+
export default defineConfig({
176+
plugins: [
177+
ImageMin({
178+
mozjpeg: {
179+
quality: 80,
180+
progressive: true,
181+
},
182+
}),
183+
],
184+
});
185+
```
186+
187+
이렇게 하면 번들 그래프에 들어온 이미지 (`import img from "./image.jpg"` 또는 CSS 의 `url()`, HTML 의 `src=""`) 들이 Progressive JPEG 로 변환되어 `dist/assets` 폴더에 저장됩니다
188+
189+
<br/>
190+
191+
### 2. WebP (Web Picture format)
192+
193+
WebP 는 구글이 개발한 이미지 포맷으로, JPEG 나 PNG 보다 더 나은 압축률과 품질을 제공합니다
194+
195+
주변 픽셀 값을 분석해서 예측하고, 실제 값과의 차이를 저장하는 방식으로 압축하기 때문에, 동일한 품질의 이미지를 더 작은 파일 크기로 저장할 수 있습니다
196+
197+
덕분에 WebP 는 JPEG 대비 약 25-34% 더 작은 파일 크기로 압축할 수 있습니다
198+
199+
WebP 는 손실 압축, 무손실 압축, 투명도, 애니메이션 등 다양한 기능을 지원합니다
200+
201+
단, Jpeg 와 달리 Progressive 렌더링을 지원하지 않습니다.
202+
이때문에 Baseline JPEG 처럼 위에서 아래로 한 줄씩 렌더링 됩니다
203+
204+
때문에 느린 네트워크 환경에서 Progressive JPEG 와 WebP 중 타켓하는 사용자 환경에 맞게 선택하는 것이 좋습니다
205+
206+
<br/>
207+
208+
### 3. AVIF (AV1 Image File Format)
209+
210+
AVIF 는 구글, 넷플릭스 모질라에서 만든 최신 이미지 포맷으로 AV1 비디오 코덱을 기반으로 WebP 보다도 더 나은 압축률과 품질을 제공합니다.
211+
212+
JPEG 대비 50% 이상, WebP 대비 20~30% 더 작은 파일 크기로 압축할 수 있습니다
213+
214+
단, 인코딩시 자원을 많이 잡아먹어 속도가 느리다는 단점이 있습니다
215+
216+
<br/>
217+
218+
### 엥? 그럼 무지성 AVIF, WebP 쓰면 되는거 아님?
219+
220+
2024년 기준으로 AVIF, WebP 는 대부분의 최신 브라우저에서 지원되지만, 몇몇 구닥다리 브라우저는 [AVIF](https://caniuse.com/avif), [WebP](https://caniuse.com/webp) 를 지원하지 않습니다
221+
222+
특히 Internet Explorer (IE) 는 AVIF/WebP 를 전혀 지원하지 않기 때문에, AVIF/WebP 만 사용하는 것은 위험할 수 있습니다
223+
224+
![뭐? 어떤 브라우저 하나를 조심하라고? - 네, IE 는 위험합니다](./img/image-format/image-7.png)
225+
226+
그래서, AVIF/WebP 를 사용하되, 브라우저가 WebP 를 지원하지 않는 경우에는 JPEG 나 PNG 같은 대체 포맷을 제공하는 것이 좋습니다
227+
228+
<br/>
229+
230+
#### (1) HTML 의 `<picture>` 태그로 WebP 와 대체 포맷 제공하기
231+
232+
가장 표준적이고 호환성이 높은 방법은 HTML 의 `<picture>` 태그를 사용하는 것입니다
233+
234+
```html
235+
<picture>
236+
<source srcset="/images/work.avif" type="image/avif" />
237+
<source srcset="/images/work.webp" type="image/webp" />
238+
<source srcset="/images/work.jpg" type="image/jpeg" />
239+
<img
240+
src="/images/work.jpg"
241+
alt="졸업 작품"
242+
width="800"
243+
height="600"
244+
loading="lazy"
245+
decoding="async"
246+
/>
247+
</picture>
248+
```
249+
250+
최신 브라우저와 같이 `image/avif` 또는 `image/webp` 를 지원하는 경우에는 AVIF/WebP 이미지를 로드하고, 그렇지 않은 경우에는 JPEG 이미지를 로드합니다
251+
252+
<br/>
253+
254+
#### (2) React 환경에서 재사용 컴포넌트로 만들기
255+
256+
React 에서도 `<picture>`를 그대로 쓸 수 있습니다.
257+
반복적으로 쓰이는 패턴이므로, 재사용 가능한 컴포넌트로 감싸두면 더 편리합니다.
258+
259+
```tsx
260+
import React from "react";
261+
262+
export interface PictureProps extends React.ComponentProps<"img"> {
263+
srcAvif: string;
264+
srcWebp: string;
265+
srcFallback: string; // JPEG
266+
alt: string;
267+
}
268+
269+
export const Picture = ({ srcAvif, srcWebp, srcFallback, alt, ...imgProps }: PictureProps) => {
270+
return (
271+
<picture>
272+
<source srcSet={srcAvif} type="image/avif" />
273+
<source srcSet={srcWebp} type="image/webp" />
274+
<source srcSet={srcFallback} type="image/jpeg" />
275+
<img src={srcFallback} alt={alt} {...imgProps} />
276+
</picture>
277+
);
278+
};
279+
```
280+
281+
<br/>
282+
283+
## 이미지 포맷별 특징 및 성능 비교
284+
285+
| 특성 | Baseline JPEG | Progressive JPEG | WebP (손실) | WebP (무손실) | AVIF |
286+
| ----------------------------------- | -------------------- | ------------------------------ | ------------------------------ | --------------------------- | -------------------------------------------- |
287+
| 파일 크기 절감 (Baseline JPEG 대비) | 기준 | 1–10% (10KB 이상 이미지) | 25–34% (동일 품질) | PNG 대비 26% | JPEG 대비 50% 이상, WebP 대비 20–30% 더 작음 |
288+
| 인식 로딩 경험 | 위에서 아래로 렌더링 | 흐릿한 전체 이미지 후 선명해짐 | 위에서 아래로 점진적 렌더링 | 위에서 아래로 점진적 렌더링 | Progressive 미지원, 단일 패스 디코딩 |
289+
| 디코딩 비용 (CPU) | 낮음 | 중간 (Baseline의 약 3배) | 낮음–중간 (JPEG보다 약간 높음) | 중간 | 중간–높음 (압축 효율 높지만 디코딩 무거움) |
290+
| 브라우저 지원 | 보편적 | 보편적 | 높음 (97% 이상) | 높음 (97% 이상) | 최신 브라우저 대부분, 일부 구형 환경 미지원 |
291+
| 투명도 지원 | 아니요 | 아니요 ||||
292+
| 애니메이션 지원 | 아니요 | 아니요 ||||
293+
294+
## 마치며
295+
296+
이미지 포맷 선택은 단순히 파일 크기 문제를 넘어 UX, 네트워크 환경, 디바이스 성능까지 직결되는 중요한 결정입니다.
297+
298+
- JPEG (Baseline/Progressive): 여전히 안정적이고 보편적
299+
- WebP: 차세대 표준으로 자리잡은 실무용 기본 옵션
300+
- AVIF: 가장 효율적인 차세대 포맷, 아직은 WebP/JPEG와 함께 다단계 폴백으로 사용 권장
301+
302+
결국
303+
304+
> "가능하면 단일 포맷에 올인하지 말고, 사용자 환경에 맞는 최적의 조합을 제공하자!" 입니다.
305+
306+
[다음 편에서는 실제 React 환경에서 이미지 지연로딩과 같은 최적화 기법을 적용하는 방법에 대해 알아보겠습니다](./image-optimization.md)
307+
308+
## 참고 자료
309+
310+
- https://web.dev/learn/images/jpeg?hl=ko
311+
- https://web.dev/learn/images/webp?hl=ko
312+
- https://web.dev/learn/images/avif?hl=ko
313+
- https://www.iostream.co/article/r-x1gVy5
314+
- https://www.thewebmaster.com/progressive-jpegs/
315+
- https://uploadcare.com/blog/the-what-why-and-how-of-progressive-jpeg/
316+
- https://www.hostinger.com/tutorials/what-is-progressive-jpeg-images
317+
- https://cloudinary.com/guides/front-end-development/webp-format-technology-pros-cons-and-alternatives
318+
- https://medium.com/@duhroach/how-webp-works-lossly-mode-33bd2b1d0670
319+
- https://developer.mozilla.org/ko/docs/Learn_web_development/Extensions/Performance/Multimedia
320+
- https://cloudinary.com/blog/progressive_jpegs_and_green_martians
321+
- https://suyeon96.tistory.com/15
1.4 MB
Loading
146 KB
Loading
69.5 KB
Loading
1.36 MB
Loading
118 KB
Loading
633 KB
Loading
598 KB
Loading

0 commit comments

Comments
 (0)