본문 바로가기

프론트엔드

이미지 용량 최적화하기(feat. picture tag, sprite image)

  • 프로젝트: loplat UI, 채식 지도
  • 키워드: picture tag, source tag, WebP, sprite image, naver map marker, image optimization
  • 상황 1

    1. loplat UI의 Spinner 컴포넌트는 용량이 큰 sprite image와 css 애니메이션을 통해 구현했다.
      (참고: css animation steps로 Spinner 구현하기)
    2. gif를 사용하지 않고 png로 구현했음에도 아직 이미지 용량이 크다는 생각이 들었다.
    3. WebP 형식을 사용하려다가 safari 브라우저에서 지원하지않아 보류했는데, 크롬에서라도 WebP 이미지를 지원하고자 한다.
  • 해결방법 1

    1. picture tag를 사용하면 browser/display마다 최적의 이미지를 제공할 수 있다는 것을 알게 되었다.(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)

    2. img tag(SpriteImage)만 있던 기존의 코드를 picture와 source를 이용하여 개선했다.
        // before
      <SpriteImage src={Cube} alt="" duration={duration} steps={steps} />
      
      // after
      <picture>
        <source srcSet={CubeWebp} type="image/webp" />
        <SpriteImage src={Cube} alt="" duration={duration} steps={steps} />
      </picture>

      picture tag 안의 source에서 CubeWebp라는 webp 이미지를 srcset으로 넘겨준다. 이때, type="image/webp"를 꼭 명시해야한다.
      브라우저가 WebP 형식을 지원하면 CubeWebp를 img의 src로 사용하고, WebP 형식을 지원하지 않으면 Cube를 src로 사용한다.
    3. 크롬에서 개발자 도구를 통해 확인한 결과, current source에 WebP 이미지가 사용된 것을 확인했다.
      current source가 webp 이미지
    4. 사파리에서 네트워크를 확인한 결과, webp 이미지는 불러오지 않고 png 이미지만 다운로드된 것을 확인했다.
      safari에서는 png만 불러옴
  • 상황 2

    1. 네이버 지도에 음식 Marker 이미지를 올리고자 한다.
    2. 수많은 종류의 마커 이미지를 하나하나 불러오면 용량이 크고 관리하기 불편하므로, 하나의 스프라이트 이미지 파일에 모든 마커를 담은 후 naver marker의 속성을 이용하여 필요한 부분만 렌더링해야한다.
      (https://navermaps.github.io/maps.js/docs/naver.maps.Marker.html)
  • 해결 방법 2

    1. 디자이너분에게 각 마커를 합친 sprite image를 요구하고 전달받았다.

      food sprite image
    2. naver marker 가이드를 참고하여 특정 index의 음식 icon을 추출하는 함수를 작성한다.
      (https://navermaps.github.io/maps.js/docs/tutorial-2-Marker.html)
        const MARKER_HEIGHT = 68; // 실제 아이콘 하나의 높이
      const MARKER_WIDTH = 48;  // 실제 아이콘 하나의 너비
      const NUMBER_OF_MARKER = 10;  // 아이콘 갯수
      const SCALE = 2 / 3;  // 줄이는 비율
      
      const SCALED_MARKER_WIDTH = MARKER_WIDTH * SCALE;  // 사용할 마커의 너비
      const SCALED_MARKER_HEIGHT = MARKER_HEIGHT * SCALE;  // 사용할 마커의 높이
      
      export function generateStoreMarkerIcon(markerIndex: number): ImageIcon {
        return {
          url: 'images/markers.png',
          size: new naver.maps.Size(SCALED_MARKER_WIDTH, SCALED_MARKER_HEIGHT),
          scaledSize: new naver.maps.Size(SCALED_MARKER_WIDTH * NUMBER_OF_MARKER, SCALED_MARKER_HEIGHT),
          origin: new naver.maps.Point(SCALED_MARKER_WIDTH * markerIndex, 0),
        };
      }
      필요한 정도의 scaled width와 height를 설정하고 size(표시할 마커 하나의 크기)와 scaledSize(사용할 이미지의 전체 크기) 속성에 넘겨준다. 그리고 사용할 음식 icon의 index에 의해 origin을 결정하면 하나의 음식 icon만 추출할 수 있다. (ex> 햄버거는 0번째, 샌드위치는 4번째)

    3. 그 후 추출한 icon과 naver maps api를 이용하여 marker를 그린다.

        marker = new naver.maps.Marker({
        map,  // naver map
        position: new naver.maps.LatLng(...coordinates),  // 지도에서 마커의 위치
        icon,  // generate한 음식 icon
      });


    4. 원하는 위치에 원하는 아이콘이 그려진다.
      food icon 렌더링 결과
  • 의의
    1. 1번 상황에서 WebP 이미지를 통해 Circle(51KB -> 14KB) 73%, Cube(28Kb -> 6Kb) 79% 용량 최적화를 했다.
    2. 2번 상황에서 스프라이트 이미지(24KB)를 통해 각 마커 이미지를 따로 받았을 때(3KB * 10)보다 20% 용량 최적화를 했다.