단순한 심볼 형태의 아이콘은 SVG로 표현해주는 것이 좋다. 벡터 기반이라 안 깨지기도 하고, 일반 그림보다 용량을 별로 차지하지 않기 때문.
하지만 의외로 React에서 SVG를 다루는 게 생각보다 귀찮은데 (svgr 설치를 하고, 코드를 작성하고 ...) 나는 간단하게 불러오고 싶어서 이것저것 알아보다가 SVG에도 Sprite 방식이 있다는 걸 알고 써먹어보는 포스팅.
Sprite는 옛날에 RPG Maker(ㅋㅋ) 좀 해봤다하는 사람들은 익숙한 용어일 것이다. Sprite는 필요한 이미지 소스들을 한데 모아서 좌표 형식으로 가져다가 쓰는 방식이다.
와 진짜 추억이다
주로 자잘한 이미지들을 모아두는 용도로 쓰인다. 자잘한 이미지들은 하나하나 불러오는 게 오버헤드가 더 걸리기 때문. 그래서 요청 한 번에 필요한 이미지를 다 가져오자~는 개념에서 시작했다.
옛날엔 Gif 하나 만들기도 꽤 복잡했던 시기가 있어서(이미지 레디라든가,, 이미지 레디라든가,,, 이미지 레디...) 로딩을 나타내는 Sprite를 만들어서 쓰기도 했었더랬다.
그리고 마찬가지로, 아이콘들을 모아두고 용도에 맞게 가져다가 쓰는 방식도 있다.
이 방식이 바로 오늘 써먹어볼 방식이다!
의외로 SVG는 Sprite를 지원하고 있는데, 바로 <defs>
태그를 이용하면 된다.
<defs>
태그 안에 선언된 녀석들은 SVG 뷰포트에 렌더링이 되지 않고, <use>
태그를 통해서만 가져올 수 있다. 가져오기 위해선 <defs>
안쪽에 선언된 컨테이너들(symbol, g, ...)은 id
가 필요하다.
그래서~ 아래와 같이 써먹어볼 수 있다.
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <defs> <symbol id="menu" viewBox="0 0 24 24"> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M4 12h16M4 6h16M4 18h16"/> </symbol> </defs> <use href="#menu" /> </svg>
이러면 SVG엔 menu Symbol이 나오는 것! 아주 편하지 않는가? 그래서 필요한 아이콘들을 다 하나의 SVG에 때려놓고 <use>
로 아이디를 선택하기만 해주면 되는 것이다.
보통 개발자들은 아이콘을 Figma에서 제공받는다. 천천히 피그마에서부터 SVG Sprite를 만들기까지 한 단계씩 밟아보자!
먼저 Figma에 일을 잘하는 디자이너라면 이렇게 아이콘을 묶어놨을 것이고, 이름도 적절하게 잘 지어놨을 것이다. 아니라면... 유감
그러면 이 각각의 아이콘들을 다 선택한 뒤에, 'SVG Export' 라는 플러그인을 실행시켜주자. 없으면 Figma Plugin에서 설치해주면 된다.
요 플러그인이 좋은 게, 이렇게 Sprite sheet로 출력할 수 있게 해준다. 이름 지정이 필요한 이유
Export all을 눌러주면 zip파일이 하나 다운로드 되는데, 그 안의 sprite.svg가 바로 우리가 원하던 파일이다!
이제 요걸 입맛에 맞게끔 요리조리 잘 수정해주면 되는데, 나는 일단 path의 색은 currentColor로 바꿔주고, 나머지는 다 없애버리는 편이다.
요로코롬. 만약 나중에 아이콘이 추가가되면 요 아래에 symbol만 추가해주면 된다.
사용하기 쉽게 컴포넌트로 하나 만들어주자.
SVGIcon.tsx
import icons from './sprites.svg'; export type IconName = | 'menu' | 'bookmark' | 'warning'; type SVGIconProp = { name: IconName; size?: number; } & SVGAttributes<SVGElement\>; export const SVGIcon = ({ name, size = 32, className }: SVGIconProp) => { return ( <svg className={className} fill="none" width={size} height={size}> <use href={`${icons}#${name}`} /> </svg> ); };
name을 받아서 sprite의 name을 사용하는 컴포넌트이다. 개인적으로 추천하는 게, IconName이라는 Type을 따로 만들어두면 아래와 같이 인텔리센스가 작동해서 더 편하다!
또, 공통으로 들어갈 스타일이나 기본 스타일은 여기에서 적용을 해주면 돼서 아주아주 편하다.
색을 바꾼다거나 다른 SVG Props을 적용해주어야 할 땐 SVGIcon에 props을 만들어서 넘겨주면 되긴 하지만, 나의 경우엔 CSS 변수에 등록된 디자인 토큰을 이용하고 있었다. 즉, CSS에서 값을 넘겨줘야 하는데 해당 코드는 svg props에 직접 조작하는 방식이라 변수를 가져와서 뭐시기뭐시기 하려면 꽤 복잡해진다.
그래서 어떻게 해야 하나~ 조금 고민했는데, utility class처럼 쓰면 되겠다 싶었다.
utility class
.filled-black { fill: var(--gray900); }
쓰는 곳에선 이렇게
<SVGIcon name="bookmark" className={styles.filledBlack} />
className이 더 직접적여서 덮어씌워진다! 캬캬
classnames 패키지를 이용하면 편하다.
CSS
.size2 { --size-2: 0.75rem; width: var(--size-2); height: var(--size-2); } .filled-black { fill: var(--gray-900); } .color-red { color: var(--negative); }
React
import cn from 'classnames' <SVGIcon name="bookmark" className={cn(styles.size2, styles.filledBlack, styles.colorRed)} />
굿