이전에 진행했던 프로젝트에서 이미지 확장자로 .png를 사용했었다. 하지만 png는 jpeg, webp에 비하면 용량이 훨씬크다.
이렇게 되면 이미지를 렌더링 하는 데 있어서도 시간이 오래걸리고 이는 유저의 페이지 이탈을 야기 할 수 있다.
우선 png가 왜 타 확장자에 비해 용량이 큰지 알아보기 위해 올리브영 기술 블로그를 참고하여 레스터 이미지와 벡터 이미지에 대해 알아보자.
✅ 레스터 이미지
래스터 이미지는 픽셀에 표현하고자 하는 색상을 그려서 이미지 형태로 표현하는 방식이다. 개발자들이 주로 사용하는 JPEG, PNG, GIF 등이 대표적인 레스터 이미지이다. 이 레스터 이미지는 여러 픽셀이 모여서 하나의 이미지를 만들기 때문에, 사이즈가 크거나 품질이 더 좋은 이미지를 만들기 위해서는 그만큼의 정보를 담은 픽셀들을 추가해야만 컴퓨터가 이를 정상적으로 표현할 수 있다. 사이즈가 커지면 커질수록 이미지의 용량도 늘어나고 렌더링 속도도 현저히 떨어지게 된다.
✅ 벡터 이미지
반면, 벡터 이미지는 픽셀값으로 그림을 그리는 게 아닌 이미지 안에 수많은 수학 방정식을 포함하고 있는 방식의 이미지 형태이다. 그렇기 때문에 컴퓨터는 선의 표현, 크기, 색상 등의 정보를 이미지안에 있는 계산식으로 연산하여 이미지를 제공한다. 이러한 연산을 사용하는 이미지 렌더링 방식 덕분에, 벡터 이미지는 사이즈가 커지거나, 작아져도 이미지가 깨지거나 정보가 달라지지 않는다. 그 말은 즉, 항상 같은 이미지 품질을 유지할 수 있다는 뜻. 이 벡터 이미지는 보통 아이콘, 폰트 등에 주로 사용되며, 대표적인 W3C 포맷인 SVG가 많이 사용된다. 하지만, 이미지가 복잡하면 복잡할수록 이미지 안에 수학적인 정보는 계속해서 늘어나게 되고, 이미지 용량 또한 같이 늘어나게 됩니다.
이어서 손실 이미지와 무손실 이미지에 대해 알아보자
✅ 무손실 이미지
레스터 이미지와 벡터 이미지와 더불어 이미지를 구분하는 또 다른 기준은 이미지의 정보 손실을 허용 하는지의 안 하는지의 여부이다. 용어만 봐서는 무손실 이미지가 원본 이미지라고 이해할 수 있는데, 사실 무손실 이미지가 원본 이미지라는 뜻은 아니다. 원본 이미지는 말 그대로 이미지의 모든 정보를 가지고 있는 이미지를 말하고, 무손실 이미지는 원본 이미지에서 이미지를 렌더링하는데 필요하지 않은 정보들을 제거한 이미지를 무손실 이미지라고 한다. 따라서 무손실 이미지는 원본 이미지보다 용량이 줄어들 수 있다. GIF, PNG가 대표적인 무손실 이미지이다.
✅ 손실 이미지
손실 이미지는 무손실 이미지의 화질 감소를 감수하면서도 사이즈를 줄여 빠른 렌더링을 할 수 있는 이미지이다. 일반적으로 사람이 품질 저하를 거의 눈치채지 못하면서 품질을 낮출 수 있는 퍼센트는 100% ~ 75% 사이라고 하는데, 대표적으로 구글 이미지의 썸네일은 74%~76% 정도의 손실 이미지를 사용하고, 페이스북 이미지는 85%의 손실 이미지를 사용한다. 손실 이미지의 사용은 무손실 이미지보다 상대적으로 빠른 렌더링이 가능하므로 사용자에게 꼭 필요한 정보를 주는 이미지가 아닌 경우 사용되는 이미지이다. JPEG가 가장 대표적인 손실 이미지이며, 그 외로 JPEG 2000등이 존재한다.
그렇다면 우리는 어떤 이미지를 사용 해야할까? 답은 정해져 있지 않다. 이미지의 렌더링은 이미지 용량에 비례하기 때문에 상황에 맞는 이미지를 사용하면 되는 것. 내가 최근 구현한 "대박사건" 프로젝트는 이미지가 크게 중요하지 않기 때문에 손실 이미지를 사용하여 화질이 감소하더라도 빠르게 렌더링 하는 것이 중요하다고 판단이 된다. 그래서 jpeg로의 변환을 고민했지만, 손실 및 무손실 압축을 모두 지원하고 이미지 품질도 유지할 수 있어 더 효율이 좋은 webp를 사용하기로 결정했다.
따라서 내가 구현한 프로젝트 대부분의 무손실 이미지인 png 형식을 압축하고, webp로 변환하는 작업을 진행했다.
하지만 이미 프로젝트에 올라가 있는 png 파일들을 하나하나 webp 형식으로 변경하기 시간이 꽤 드니, 자동화를 하면 어떨까라는 생각이 들었다. 우리 프로젝트는 모듈 번들러로 vite를 사용하고 있으니 vite의 config 파일을 plugin을 사용하여 수정 해보도록 하자.
Vite config 수정
아래 사진은 vite.config.ts 파일이다. `imagemin-webp`, `imagemin-pngquant`, `@vheemstra/vite-plugin-imagemin`을 install하여 자동 압축 및 확장자를 변환시켜주었다. viteImagemin 내부의 plugins에는 png파일을 압축할 수 있도록 해 주었고, makeWebp 내부에는 png 파일을 webp로 변환이 될 수 있도록 코드를 작성하여 주었다. `imagemin-webp`, `imagemin-pngquant`, `@vheemstra/vite-plugin-imagemin` 는 아래 링크에 들어가 읽어보고 적용하면 좋을 것 같다.
- @vheemstra/vite-plugin-imagemin
이처럼 코드를 작성한 후, 빌드를 하면 다음과 같이 압축 및 형식 변환이 적용된 파일과 전체적으로 몇 퍼센트 최적화했는지 알 수 있다.
이미지 최적화가 여기서 끝이 아니라고?
이미지 최적화는 여기서 끝이아니다. 우선 브라우저 사이즈에 맞게 적절한 이미지를 제공해 주어야 한다.
`srcset`을 통해 브라우저에게 제시할 이미지 목록과 그 크기를 정의하고, 기본적인 구조는 (이미지 url + 이미지 크기)로 되어있는데 여기서 이미지 크기를 표현하는 단위는`px`이 아닌 `w`를 사용한다. `w`는 이미지의 고유 픽셀 너비로, 이미지의 실제 사이즈를 말한다.
`sizes`는 미디어 조건문을 정의하고 특정 미디어 조건문이 참일 때 어떤 이미지 크기가 최적인지를 나타낸다. 기본적인 구조는 (media 조건)이 참인 경우 이미지가 채울 슬롯의 너비로 작성할 수 있다.
예를 들면 아래와 같은 코드를 짤 수 있고, 브라우저의 동작은 다음과 같다.
<img srcset="./small.png 400w,
./medium.png 800w,
./large.png 1000w"
sizes="(max-width: 758px) 400px,
(max-width: 1024px) 800px,
1000px"
src="./large.png"
alt="IMAGE" />
- 기기 너비 확인
- sizes에서 참인 조건문 찾기
- 해당 미디어 쿼리가 제공하는 슬롯 크기 확인
- 해당 슬롯 크기에 가장 큰접하게 맞는 srcset에 연결된 이미지 렌더링
srcset을 지원하지 않는 브라우저의 경우 src에 정의된 이미지를 렌더링한다.
하지만 우리 프로젝트는 고정된 width, height 값을 가지고, 반응형으로 일정 width 이하로 떨어지면 이미지를 아예 보여주지 않게 되므로 srcset, sizes 속성은 필요가 없다. 따라서 width, height를 고정하여 사용하였다. 여기서 한가지 중요한 것은 webp 같은 차세대 형식을 html에서 보여주려면 Img 태그를 picture태그로 감싸주어야 한다. picture 태그는 내부에 있는 source 태그들과 img 태그 중에서 조건에 맞는 이미지만 로드해 주는데, 나는 반응형으로 보여주지 않기 때문에 source 태그는 사용하지 않았다.
이로써 아래의 lighthouse 성능 측정 결과 나왔던 아래 사항들 중 웹폰트를 제외한 나머지를 해결하여 총 147KiB의 용량을 줄일 수 있었다. 미미하지만 이러한 최적화가 하나 둘 씩 쌓여 유저에게 좋은 경험을 줄 수 있지 않을까 싶다.
'React' 카테고리의 다른 글
0.1초라도 빠르게 데이터 보여주는 방법 by TanStack Query(prefetchQuery) (0) | 2024.05.29 |
---|---|
렌더링을 빨리 빨리!! by 번들 사이즈 최적화 (2) | 2024.04.25 |
프론트엔드 성능 최적화(Font) (0) | 2024.04.12 |
human error를 줄여 동료 개발자의 경험을 개선해보자! with console.log() (0) | 2024.04.08 |
OAuth 로그인 구현기 (2) | 2024.04.04 |
같은 실수를 반복하지 않고, 한 번 학습한 내용을 오래 기억하기 위해 개발하면서 겪었던 트러블 슈팅과 학습한 내용을 정리하고 기록합니다 🧑💻