PNG Image Sprite에서 SVG Symbol Sprite 적용하기

허재원·2023년 2월 10일
1

리팩토링 전

웹 접근성을 위해 아이콘 버튼 IR 기법을 활용하기

// components/common/Header
<S.MenuButton>
  <span className='ir'>메뉴 열기 버튼</span>
</S.MenuButton>
<S.SearchButton>
  <span className='ir'>검색 버튼</span>
</S.SearchButton>
// components/common/Header/style.ts
export const SearchButton = styled.button`
  display: none;

  @media screen and (max-width: ${({ theme }) => theme.bp.lg}) {
    display: block;
    width: 2.8rem;
    height: 2.8rem;
    background: url('/assets/icons/sprite-icons.png') no-repeat -2.8rem / 5.6rem 2.8rem;
  }

  @media screen and (max-width: ${({ theme }) => theme.bp.md}) {
    width: 2.4rem;
    height: 2.4rem;
    background: url('/assets/icons/sprite-icons.png') no-repeat -2.4rem / 4.8rem 2.4rem;
  }
`;

위 코드는 웹 접근성을 위해 아이콘 버튼 IR 기법을 활용하기에서 추가로 여러 아이콘을 이용한 svg 이미지 포맷을 png 이미지 포맷으로 바뀌 IS(Image Sprites) 기법을 이용한 코드이다.
근데 아이콘 같은 경우는 png 이미지 포맷 보다는 svg를 이용한다. 그래서 svg를 통해 이미지 스프라이트 기법을 이용할 수 있지 않을까 하고 찾아봤다.

SVG(Scalable Vector Graphics)

출처: SVG 구조, 렌더링 성능 알고 쓰기

SVG는 2차원의 벡터 그래픽을 표현하기 위한 XML 기반의 파일 형식이다. 비슷한 이미지 파일 형식인 PNG는 비트맵 기반의 이미지로, 각 픽셀에 저장된 비트 정보가 집합된 형태로 구성된다. 픽셀들의 배열 방식과 픽셀의 숫자와 비율이 디스플레이의 해상도를 결정하는데, 픽셀의 수가 많아질수록 화질은 뛰어나지만 용량 또한 커지게 된다. 또 해상도 이상으로 이미지를 확대하거나 축소하면 일반적으로 말하는 깨짐 현상이 발생할 수밖에 없다.

반면 SVG는 그래픽의 형태들이 고정된 비트맵을 가진 것이 아니라 수학적 점수를 이용하여 도형이나 선을 그린다. 두 개의 점에 대한 정보를 갖고 있으면 선을 표현할 수 있고, 세 개 이상의 선에 대한 정보를 갖고 있으면 면을 형성할 수 있다. 벡터 그래픽은 실제로 이미지에 표현되는 점에 대한 정보를 저장하여 수학적 공식에 따라 점과 점을 연결하고, 선의 두께, 색상, 곡률 등을 값으로 지정해 이미지를 표현하는 방식이다. SVG는 XML 기반 언어이기 때문에 사람이 코드를 읽기 쉽고, 개발자가 바로 수정이 가능하다는 장점이 있다.

svg 스프라이트 적용하기

출처: SVG 스프라이트 기법으로 사이트 성능 향상시키기

Symbol Sprite

SVG 파일을 하나의 파일로 관리하기 위해 Spritebot 툴을 사용했다.

<svg xmlns="http://www.w3.org/2000/svg">
  <symbol id="icon-menu" viewBox="0 0 24 24">
    <path stroke="#000" strokeLinecap="round" stroke-miterlimit="10" stroke-width="1.5" d="M3 10h18M3 6h18M3 14h18M3 18h18"/>
  </symbol>
  <symbol id="icon-search" viewBox="0 0 24 24">
    <path fill-rule="evenodd" d="M15.507 14.006h-.79l-.28-.27a6.475 6.475 0 0 0 1.57-4.232 6.504 6.504 0 1 0-6.503 6.503c1.61 0 3.091-.59 4.232-1.57l.27.28v.79L19.01 20.5l1.491-1.49-4.993-5.004zm-6.003 0A4.497 4.497 0 0 1 5 9.504 4.497 4.497 0 0 1 9.504 5a4.497 4.497 0 0 1 4.502 4.503 4.497 4.497 0 0 1-4.502 4.502z"/>
  </symbol>
</svg>

SVG를 표현하는 symbol 엘리먼트를 만들고, 개별 아이콘은 해당 symbol을 참조하여 표시된다. 이 방법의 성능은 렌더링하는 엘리먼트의 개수에 따라 영향 받는다.

SVGIcon 컴포넌트 생성

import type { TIconProps } from 'types/icon';
import spriteSVG from 'assets/icons/sprite-icons.svg';

export const SVGIcon = ({ id, size }: TIconProps) => {
  return (
    <svg width={size} height={size}>
      <use href={`${spriteSVG}#${id}`} />
    </svg>
  );
};

SVGIcon 컴포넌트에 id를 props로 내려주면 해당 아이콘 svg가 보이는 컴포넌트를 생성했다.

SVGIcon 컴포넌트 적용

// components/common/Header
<S.MenuButton>
  <span className='ir'>메뉴 열기 버튼</span>
  <SVGIcon id='icon-menu' size={24} />
</S.MenuButton>
<S.SearchButton>
  <span className='ir'>검색 버튼</span>
  <SVGIcon id='icon-search' size={24} />
</S.SearchButton>
export const MenuButton = styled.button`
  display: none;

  @media screen and (max-width: ${({ theme }) => theme.bp.lg}) {
    display: block;
  }
`;

background로 처리했던 이미지를 다음과 같이 작성하여 좀 더 가독성이 있고, IR 기법을 활용한 마크업을 작성해 해당 아이콘이 무엇인지 스크린리더가 읽거나 브라우저에 정보를 제공할 수 있도록 했다.

0개의 댓글