Post

Image에 대해 알아볼까요?

들어가며

많은 이미지를 디스플레이하는 일이 생기면서 이미지에 대한 궁금증이생겨서 이 글에서는 웹개발에 있어 이미지 최적화의 필요성을 짚어보고, 다양한 이미지 포맷 (jpeg, png, webP 등), 웹에서 이미지를 다루는 방식 (blob, base64 등), 최적화 기법 등의 이미지를 효율적으로 관리하고 최적화하는 방법들에 대해 작성해보려고한다.

이미지 최적화를 왜 할까?

이미지를 많이 업로드하는 사이트에 경우에는 파일 크기에 따라 렌더링 시간이 천차만별이다. 이 글을 보는 모든 분들이 아시다시피 렌더링 시간은 UX에 미치는 영향이 굉장하다.

아래의구글의 연구 결과에 따르면 페이지 렌더링 시간이 길어질수록 사이트 이탈율이 굉장히 늘어는걸 알 수 있다.

graph

물론 로딩이나 skeleton UI 등 여러 방식으로 사용자 경험을 향상시킬 순 있지만, 근본적인 원인을 해결하려면 렌더링 속도를 단축시키는게 더 유의미한 작업이라 생각할 수 있다.

렌더링 속도를 단축시키는 것은 여러가지 방법이 있다. 프론트엔드에서의 렌더링 최적화 에 대해서는 이미 포스팅한 내용이 있기 때문에 참고하면 좋을거 같다.`

그러므로 렌더링에서 가장 큰 부분인 이미지의 용량을 최적화 할 수 있다면 렌더링 속도를 유의미하게 단축시킬 수 있다.

이미지 압축 방식

png, jpeg, bmp, svg, webP 등 굉장히 많은 이미지 형식이 있는데 크게 이미지 형식(bitmap, vector), 또는 압축을 하는 방식으로 구분할 수 있다.

무손실 압축

무손실 압축은 모든 데이터의 모든 정보를 보존하는 압축하는 방식이다. 원본 이미지 데이터의 구조나 패턴을 분석해서 중복되는 정보를 줄이는 방식으로 이루어진다. 품질이 저하되지 않고 이미지를 저장할 수 있으며, 또한 압축을 해제하면 동일한 이미지를 복원할 수 있다 이러한 특성 때문에 손실 압축에 비해 상대적으로 압축률이 떨어진다 일반적으로 50% ~ 33% 정도의 압축률을 가지고 달성할 수 있고 반복되는 픽셀과 패턴이 많을 경우 더 높은 압축률을 달성할 수 있따.

손실 압축

손실 압축은 일부 데이터가 훼손되는것을 감안하고 영구적으로 제거하여 파일의 크기를 줄인다. 이 과정에서 원본 이미지에 비해 품질이 다소 감소할 수 있고, 극단적인 압축을 했을 때는 90% 이상의 압축률을 달성할 수 있지만 이미지 품질이 심하게 저하된다. 사람이 육안으로 판단할 수 없는 이미지 압축률은 70% ~ 85% 정도의 압축률이다.

아래는 이미지 포맷들의 특성과 압축방식 등을 정리한 표이다

방식압축방식특성
png무손실웹에서 많이 사용되는 형식이라 할 수 있고, 투명도를 지원한다.
jpeg손실가장 많이 사용되는 이미지 포맷이다.
bmp비압축Bitmap Image File는 압축을 하지 않거나 굉장히 간단한 압축을 하기 때문에 이미지를 편집하거나 처리할 때 접근이 빠르고 용이하다. Windows 환경에서 기본적인 이미지 형식으로 많이 사용된다.
svg무손실vector 방식의 이미지 포맷으로 방정식 등의 수학적 정보로 표현되고, 확대해도 품질이 저하되지 않는다.
webP손실/무손실google에서 만든 최신 이미지 형식 무손실과 손실 압축을 모두 제공하며 무손실은 PNG보다 26%, 손실은 JPEG보다 25~34%가 더 작다는 google에서 발표한 비교평가를 확인할 수 있다.

웹에서 이미지를 다루는 방식

Blob (Binary Large Object)

Blob은 Binary Large Object란 이름과 같이 이미지, 오디오, 비디오 와같은 멀티미디어 파일들을 Blob 객체로 변환하여 처리할 수 있다. (string, number 와 같은 타입의 일종이다.)

Blob은 파일 시스템에 직접 저장되는 파일이 아니기 때문에 메모리 내에서 관리가 가능하고, 그러한 특성으로 인해 사용자가 web에서 업로드한 파일을 미리보게 할 수 있게 하던가, 동적으로 생성된 데이터를 사용자에게 다운로드하게 하는데 용이하다.

blob을 web에서 사용하기 위해서는 바로 img src에 넣을 순 없고 url로 변환해주는 과정이 필요한다. 그리고 메모리에 저장되는 특성 때문에 URL을 한번 만들게 되면 URL이 해제되기 전까지는 js 엔진에서 URL이 유효하다 판단하기 때문에 img를 보여주지 않고, 다운로드 혹은 전송용이라면 작업이 끝난 뒤에 명시적 해제가 필요하다.

1
2
3
4
5
6
7
// blob to url
const url = window.URL.createObjectURL(blob);

// 명시적 해제
window.URL.revokeObjectURL(url)

<img src={url}/>

Base64

Base64는 데이터를 64진법으로 나타내는 것이며, 이미지 데이터(binary Data)를 ASCII 영역의 문자들로 이루어진 일련의 텍스트 기반 포맷으로 인코딩한 것이다.
여기서 착각할 수 있는데 링크와 base64는 다르다 링크는 온전히 이미지 데이터 위치를 가져오는 것 뿐이고, base64는 실제 이미지 데이터를 가지고있다.

그래서 Url과 다른 장점은 별도로 이미지가 저장되어 있지 않아도 된다 base64 데이터가 이미지를 문자열을 만든 것이기 때문이다.

사용법은 아래와 같다.

1
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD..../>

이미지 최적화 기법

먼저 이미지 포맷과 이미지를 어떤식으로 다루는지 알게 되었으니 본인에게 적절한 이미지를 형식을 선택하고 최적화를 진행할 차례다.

이미지 압축 도구 사용

먼저 react같은 경우 webPack과 같은 번들러를 사용할 때 image-webpack-loader 같이 자동으로 이미지를 최적화 해주는 도구가 있다. 또한, TinyPng 같은 npm 라이브러리를 사용하거나 1차원 이미지 배열일 땐 rle 같은 압축 기법을 사용해 볼 수 도 있다.

이미지 Sprite

이미지 Sprite 기법은 여러 이미지 파일을 하나의 이미지로 합쳐서 http 요청을 줄이고 css에서(background-position)를 조정해서 개별 이미지를 표시할 수 있다.

sprite

img 태그의 srcset속성을 사용하기

img 태그의 srcset을 이용하면 다양한 크기와 비율의 이미지들을 명시하고, css의 미디어 쿼리와 같은 효과를 낼 수 있다.
아래와 같이 설정하면 화면에 너비에 따라 500px, 1000px, 1500px 너비의 이미지 중 하나를 선택한다. (srcset 값은 w 디스크립터 또는 x 디스크립터로 지정해야한다.)

1
2
3
4
5
6
7
8
9
<img
  src="example-small.jpg"
  srcset="
    example-small.jpg   500w,
    example-medium.jpg 1000w,
    example-large.jpg  1500w
  "
  alt="예제 이미지"
/>

picture 태그 사용하기

picture 태그는 img 태그보다 더 상세한 이미지 선택 조건을 지정하고 싶을 때 사용할 수 있다. source 태그들을 포함할 수 있으며 다른 미디어 조건과 함께 srcset을 사용할 수 있다.(이미지 포맷, 특정화면 크기, 화면 해상도 등)
picture tag는 아래와 같이 설정할 수 있다.

1
2
3
4
5
6
// 아래 코드는 webP 포맷을 지원할 경우 webP 이미지를 로드하고 그렇지 않으면,
JPEG 이미지를 로드하는 코드이다.
<picture>
  <source type="image/webp" srcset="image.webp" />
  <img src="image.jpg" alt="대체 텍스트" />
</picture>
1
2
3
4
5
6
// 아래 코드는 화면 너비에 따라 다른 이미지를 로드한다.
<picture>
  <source media="(min-width: 800px)" srcset="large.jpg" />
  <source media="(min-width: 480px)" srcset="medium.jpg" />
  <img src="small.jpg" alt="대체 텍스트" />
</picture>

지연로드 (Lazy Load) 사용하기

LazyLoad는 이미지 뿐만 아니라 많은 데이터를 보여줘야할 때 화면에 보이는 공간에만 데이터를 가져오는 최적화 기법이다.

이미지는 여러가지 방식으로 구현이 가능한데 첫번째론 <img> 태그에서 제공하는 loading 속성을 사용하는 것이다. 브라우저 내장 기능으로 사용자의 ViewPort에 해당 요소가 들어오기 전까지 로딩을 연기한다. 사용법이 간단해서 쓰기 좋지만, 단점도 몇가지 있다. 먼저 IE와 오래된 브라우저는 지원하지 않는다. (polyfill로 어느정도 해소가 가능하다.) 또한 사용자 정의 제어는 불가하고, 지연 로딩으로 인해 SEO 성능에 영향을 줄 수 있다.

사용법은 아래와 같다.

1
2
<img src="example.jpg" loading="lazy" alt="example/>

<img>태그 말고도 javascript로 lazy loading을 구현할 수 있는데 Intersection Observer API를 사용해서 구현할 수 있다. 스크롤되거나 화면의 사이즈가 변경되는 것을 감지하여 요소가 뷰포트에 들어가게 될 때 임시 이미지를 실제 이미지 경로로 변경하는 방식이다.

사용법은 아래와 같다.

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
32
33
34
35
36
37
38
39
document.addEventListener("DOMContentLoaded", function () {
  let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
  let active = false;

  const lazyLoad = function () {
    if (active === false) {
      active = true;

      setTimeout(function () {
        lazyImages.forEach(function (lazyImage) {
          if (
            lazyImage.getBoundingClientRect().top <= window.innerHeight &&
            lazyImage.getBoundingClientRect().bottom >= 0 &&
            getComputedStyle(lazyImage).display !== "none"
          ) {
            lazyImage.src = lazyImage.dataset.src;
            lazyImage.classList.remove("lazy");

            lazyImages = lazyImages.filter(function (image) {
              return image !== lazyImage;
            });

            if (lazyImages.length === 0) {
              document.removeEventListener("scroll", lazyLoad);
              window.removeEventListener("resize", lazyLoad);
              window.removeEventListener("orientationchange", lazyLoad);
            }
          }
        });

        active = false;
      }, 200);
    }
  };

  document.addEventListener("scroll", lazyLoad);
  window.addEventListener("resize", lazyLoad);
  window.addEventListener("orientationchange", lazyLoad);
});

마치며

오늘은 이미지 포맷과 웹에서는 이 이미지를 어떻게 다운로드하고 디스플레이하는지 알아봤습니다. base64, blob을 쓰고는 있었지만 정확히 어떻게 다른건지 어떤 상황에 써야하는지 몰랐던 분들과 많은 이미지를 디스플레이 하여 성능의 최적화가 필요한 사람들에게 도움이 되길 바랍니다.

This post is licensed under CC BY 4.0 by the author.