목차
1️⃣ 트러블 슈팅
2️⃣ 어떻게 할 수 있는데?
3️⃣ 이미지? 압축?
4️⃣ JavaScript로 이미지 압축하기
5️⃣ 결론
1️⃣ 트러블 슈팅
현장실습 근무를 하며 이미지 에셋의 크기를 줄여 로드 시간을 줄이고자 하였다.
이후에 🔗 피클을 보니, 이미지가 뚝뚝 끊겨 페인팅 되는 이슈가 눈에 띄더라..
웹페이지에서 대부분의 용량을 차지하는 건 이미지이다.
이미지를 최적화 한다면, 불필요한 대역폭을 줄일 수 있고 이를 통해 업로드 작업도 빨라지기 때문에
파일을 로드하여 화면을 빠르게 그리는 것 뿐만 아니라 통신 속도 역시 향상된다. SEO 향상까지!
(사실 Nextjs를 사용하면 🔗 next/image를 활용하면 대부분 해결되지만, 호기심이 생겨버렸다)
- 절대적인 폭을 조절한다
- 포맷을 변경한다
- img 태그의 사이즈를 지정한다
- 여러 버전의 이미지를 제공한다
- lazy하게 로딩한다
- CDN을 사용한다
- ...
최적화를 위한 여러 방안들이 있었고,
나는 "사용자가 업로드하는 이미지 파일의 사이즈"에 집중했다.
사용자가 업로드하는 이미지는 각양각색이기 때문에,
보는 데에 불편함이 없을 때까지 품질을 조정하여 이미지를 압축하면 큰 리소스 절감을 불러올 수 있었다.
특히, 80픽셀의 작은 프로필 이미지는 이미지의 퀄리티가 낮아도 시각적으로 크게 차이가 없지 않을까? 생각했다.
2️⃣ 어떻게 할 수 있는데?
이미지 압축을 도와주는 대표적인 라이브러리로는
- browser-image-compression
- compressorjs
등이 존재하여 “간단한 방법으로” 기능구현이 가능하다.
두 라이브러리를 뜯어보며 코드를 보니 주 원리는 같더라.
이 뿐만 아니라 이미지를 압축하는 convertor 사이트들(Squoosh)도 많다.
무슨 원리일까?
이를 살펴보기 전에, 배경 지식을 먼저 살펴보자.
3️⃣ 이미지? 압축?
이미지는 2차원 데이터에 기반하여 이루어진다.
이런 식으로 작은 픽셀단위로 색을 데이터로써 표현한다
정확히는, RGB 각각 1Byte 씩, 한 점에 3Byte의 데이터를 필요로 한다.
이를 통해, 가로 픽셀 * 세로 픽셀 * 3Byte 의 크기가 이미지의 용량이 된다고 유추할 수 있겠다 (파일 형식에 따라 다르겠지만,,)
몇 개의 픽셀을 뭉치면 이미지의 화질이 낮아지고, 용량이 절감되는 것 또한 유추할 수 있을 것이다.
이미지 처리는 공학분야에서 지속적으로 연구 중인 분야이다.
파일의 형식에 따라, 각 라이브러리에 따라서도 이미지를 압축하는 방법이 다르다.
좀 더 정밀한 JPEG의 이미지 압축이론에 대한 🔗 영상을 참고하며, 해당 내용은 넘어가자.
4️⃣ JavaScript로 이미지 압축하기
이미지 압축을 HTML과 JavaScript의 빌트인 API를 활용하여 직접 구현해보자.
1. 업로드한 파일 데이터를 Blob API로 변환하기
업로드한 파일 데이터를
🔗 createObjectURL 메소드로
Blob을 만들고, 해당 URL을 가져온다. 이 URL 은 커스텀 할 Image 객체에 사용된다.
여기서 🔗 Blob 객체란 파일과 유사한 불변의 객체로써,
텍스트나 바이너리 데이터로 읽히거나 ReadableStream으로 변환되어 파일을 처리하는 데에 사용된다.
우리가 이미지, 영상 혹은 오디오 등의 파일을 처리할 때 필수적으로 사용하는 API이다. (🔗 영상 처리하기 포스팅)
JavaScript에서 우리가 잘 아는 File 인터페이스 역시 Blob 기반으로 이루어져 있다.
2. 생성한 Blob URL source를 이용하여 Image 객체를 생성한다.
생성한 Blob URL을 소스로 이미지 객체를 선언한다.
image가 load되면, 메모리 누수를 방지하기 위해 revoke 해주어야 한다.
콜백함수 내에서, canvas와 함께 조작한다.
3. canvas API를 생성하고, 이미지의 크기에 맞게 조정한다.
canvas에 2D context 객체를 생성하여 이미지를 그린다.
여기서 🔗 Canvas API는 JavaScript 및 HTML 요소를 통해 그래픽을 그리는 방법을 제공한다.
더 나아가 웹 3D 기술들 역시 canvas 기반으로 이루어진다.
4. 그린 canvas를 기반으로 Blob으로 변환하여 export한다.
🔗 canvas의 toBlob 메서드를 통해 파일의 이름, 파일 형식과, 유실율까지 조정할 수 있다.
🔗 이 과정에서 canvas element의 비트맵을 복사하며 2차원 데이터 기반의 Blob을 만들 때, 유실율을 지정할 수 있고 압축이 진행되게 된다.
이후의 콜백함수를 통해 압축된 파일을 이용하여 렌더링, 다운로드, 혹은 서버 통신을 할 수 있게 된다.
주 원리가 되는 toBlob 메서드의 저성능 Polyfill은 다음과 같다.
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++ ) {
arr[i] = binStr.charCodeAt(i);
}
callback( new Blob( [arr], {type: type || 'image/png'} ) );
}
});
}
5️⃣ 결론
이미지를 최적화하는 방안을 학습하는 중에 HTML과 JavaScript로만 이미지를 압축하는 방법론을 보고 흥미로워서 꼭 글로 남기고 싶었다.
이를 직접 활용하는 것도 좋지만, 서비스의 부가적인 부분을 담당하는 기능이니 잘 짜여진 라이브러리에 의존하는 것도 좋겠다 생각하여 당장 프로젝트에 적용하였다.
라이브러리 내부에는 사이즈 조정과, target size가 될 때까지 반복해서 압축을 진행하는 등의 부가 작업까지 수행해주었다.
비동기 처리를 직접 감싸주어 사용하여, 편리하게 적용할 수 있었다. (🔗 PR 보기)
요즈음 오픈 소스를 살펴보는 일, 남들의 생각이 담긴 코드를 살펴보며 시각을 넓힐 수 있는 게 즐겁게 다가온다.
나도 남들이 내 코드를 보며 도움을 얻어가게끔 하는 개발자가 되고자 한다.
'JS, TS' 카테고리의 다른 글
[Javascript] Map & Set 객체 살펴보기 (2) | 2022.05.30 |
---|---|
[Javascript] call & apply 로 this 특정시키기 (0) | 2022.05.11 |
[Typescript] Enum, Union & intersection Type (3) | 2022.04.14 |
[Javascript] Event Delegation / 이벤트 위임 (0) | 2021.10.10 |
[Javascript] Promise와 Async / Await (0) | 2021.09.05 |