🔵

Svg는 어떻게 사용해야할까?

Created
Apr 24, 2024 06:37 AM
설명
Next.js에서 Svg 아이콘이 깜빡이는 문제를 SVGR로 해결 했습니다. 그치만 SVGR도 단점이 있다고 합니다. 어떤 방식으로 Svg를 사용해야 좋을까요?
Status
작성 완료
Tags
Next.js
Gloddy
UX
Select
Dev Log
notion image

Svg가 뭔가요?

MDN 문서의 말을 빌리자면 다음과 같습니다.
SVG는 XHTML과 비슷한 XML 언어로 오른쪽에 있는 것과 같은 그래픽을 그리는 데 사용된다. SVG 이미지는 필요한 모든 선과 모양을 직접 일일이 지정하거나, 이미 존재하는 래스터 이미지를 수정하거나, 이 두 가지 방법을 조합해서 만들 수 있다. 이미지와 그 구성요소들은 변형되고, 조합되고, 필터링될 수 있으며, 이를 통해 원래의 형태를 완전히 바꿀 수도 있다. - MDN
이런 Svg를 사용하는 방법에는 무엇이 있을까요?

Svg 렌더링 방식

우선 Svg의 여러가지 사용방법 중 대표적인 몇가지를 알아보겠습니다.

인라인 Svg

인라인 Svg는 말 그대로 Svg 콘텐츠가 HTML에 직접 포함 시키는 방식을 말합니다.
아이콘이 동일한 페이지에서 여러 번 나타나면 해당 내용이 매번 반복됩니다.
<svg viewBox="0 0 24 24" width="24" height="24"> <!-- paths, shapes, etc. --> </svg>
React 에서는 이런식으로 컴포넌트로 만들어 사용할 수 있습니다.
하지만 전체 Svg 콘텐츠를 반환하는 수동으로 작성된 React 컴포넌트는 마이그레이션이 힘들어 안티 패턴 이라고 합니다.
export default GloddyIcon = (props) => ( <svg viewBox="0 0 300 300" {...props}> <!-- paths, shapes, etc. --> </svg> );
그래서 이런점을 해결하기 위해 Svgr 같은 라이브러리가 있는데요.
Svgr.svg 파일에 대해 import가 동작하도록 번들러에게 자바스크립트가 아닌 파일을 어떻게 처리해야 할지 알려주고 .svg 파일을 리액트 컴포넌트로 변환합니다.
이는 SVG 태그의 속성(fill="red" 같은)을 손쉽게 추가할 수 있도록 해줍니다.
// App.jsx import HeartIcon from "./HeartIcon.svg"; const App = () => <HeartIcon fill="red" />;
전체 SVG 콘텐츠를 반환하는 수동으로 작성된 React 구성 요소는 소개에 작성된 이유와 svgr과 비교하여 마이그레이션하기가 더 어렵기 때문에 웹에 대한 안티 패턴이라고 합니다.

Symbol Sprite

Symbol Sprite는 단일 Svg 파일에서 symbol 로 나누어진 아이콘들을 use 를 통해 원하는 아이콘의 id를 참조해 가져올 수 있는 방식입니다.
sprite 파일은 아이콘이 여러개인 페이지에서도 1회만 다운로드 받고 중복되는 코드를 줄일 수 있습니다.
또 사용측면에서도 id를 통해 참조하기에 컴포넌트로 분리하기 쉽고 재사용이 용이합니다.
<svg style="display:none"> <symbol id="example" viewBox="0 0 24 24" width="24" height="24"> <!-- paths, shapes, etc. --> </symbol> </svg> <svg><use href="#example"/></svg>

이미지

모든 이미지 형식과 마찬가지로 Svgimg 를 통해서 표시할 수 있습니다.
다만 이 경우에는 아이콘의 색상을 변경할 수 없습니다.
<img src="path/to/icon/color.svg" alt="">

각 방식의 성능 비교

각 방식의 성능을 자세히 분석한 글이 있는데요 더욱 자세한 내용은 아래에서 확인이 가능합니다!
다음은 1,000개의 아이콘을 서로 다른 방식으로 렌더링한 결과를 ms 단위로 표시한 표 입니다.
속도 측면에서는 Image + Data URI 방식이 가장 빨랐지만 img 태그이기에 Svg의 많은 기능을 지원하지 못하기 때문에 Svg를 사용하는 의미가 없습니다.
그렇다면 Svg의 기능을 모두 지원하고 성능도 좋은 Inline SvgSymbol Sprite 방식을 사용하는게 좋아보입니다.
notion image

사용이 용이하고 성능도 괜찮은 Symbol Sprite가 짱짱맨 아님?

앞서 렌더링 방식을 살펴볼때 Symbol Sprite의 사용성을 칭찬했습니다.
제가 활동하는 Gloddy 팀에서 Icon을 렌더링할 때 바로 이 방식을 사용했습니다.
사용측에서 props 를 통해 Svg 다양한 값을 전달할 수 있고 굉장히 편리하게 사용이 가능한데요.
export default function Icon({ id, width = 24, height = 24, fill = 'none', className, ...rest }: IconProps) { return ( <svg width={width} height={height} fill={fill} className={cn('shrink-0', className)} {...rest}> <use href={`/sprite/sprite.svg#${id}`} /> </svg> ); } <Icon id="24-notification" />
하지만 이 방식도 단점이 존재했습니다.
Gloddy 에서 어떤 이슈가 있었고 어떤 방식을 취했을까요?

아이콘이 깜빡여요

Gloddy 운영 중 유저분들이나 팀원 분들의 피드백으로 아이콘이 깜빡이는 현상을 전달받았습니다.
브라우저 환경에서는 큰 문제가 없었는데 Gloddy는 모바일 웹 뷰 환경에서 동작하다 보니 들쭉날쭉한 네트워크 환경과 기기의 성능 차이 등 여러가지 요소로 인해 발생한 현상으로 추측했습니다.

성능 분석

정확한 원인을 분석하기 위해 크롬 개발자 도구에서 살펴 보았는데요.
네트워크와 스크린샷을 살펴보면 css, font, js등 필요한 리소스를 다운로드 받고 나서 sprite.svg를 다운로드 받고 브라우저의 페인팅 시점에 그려집니다.
sprite.svg가 캐시 되어 있더라도 서버측 렌더링 결과를 표시한 다음에 클라이언트측 렌더링 시점Svg를 그리게 됩니다. 이 잠깐의 틈에 아이콘이 비어있게 됩니다.
notion image
 
notion image

왜 서버측 렌더링에 아이콘이 포함되지 않았을까?

왜 서버측 렌더링에 아이콘이 포함되지 않았는지 알기 위해선 use태그의 동작을 알아야 했습니다.
Svg 스팩을 정확히 알기위해 공식 문서를 확인했습니다.

use 태그

use 는 전달받은 id와 일치하는 id를 가진 Svg 내부 symbol 요소를 참조하게 됩니다.
참조된 요소는 복사본이 생성되는데 그림자 트리(shadow tree) 형태로 정의된다고 합니다.
그림자 트리의 요소들은 use 태그의 자식 요소처럼 렌더링이 되지만 실제 Dom에는 포함되지 않습니다.
notion image
또, 공식 문서에 이렇게 적혀있는데요.
스크립팅과 문서 객체 모델을 지원하는 사용자 에이전트는 이 섹션과 [dom] 명세에 따라 그림자 트리를 구현해야 합니다. 반면에 동적 상호 작용 모드를 지원하지 않는 사용자 에이전트는 그림자 DOM의 모든 세부사항을 구현할 필요가 없을 수 있습니다.
정적인 페이지만 생성하는 서버측 렌더링에서는 이런 이유들로 아이콘이 포함되지 않았습니다.

Inline SVG

결국 이 문제를 해결하기 위해선 몇몇 아이콘을 Inline Svg 방식으로 변경해야했습니다.
Svgr 라이브러리를 사용해 변경했고 다음과 같이 서버측 렌더링에 아이콘을 포함 시키면서 아이콘의 깜빡임도 제거되었습니다.
notion image
notion image

그래서 어떤 방식을 사용해야하나?

이 질문에 해답이 도움이 되었던 글이 있는데요. 이 글도 읽어보는걸 추천합니다!

은탄환은 없다.

개발에 있어서 많은 부분이 그러하듯 방법은 많고 때에 따라 적절한 기술을 취사 선택해야 합니다.
이번에 알아본 Svg를 사용하는 방법도 마찬가지이죠.
제가 추천한 글에서는 이런 선택을 도와줄 수 있는 예시 가이드라인도 제공하고 있습니다.
notion image
Inline Svg는 속도가 빠르지만 Symbol Sprite방식 보다는 사용성이 떨어지고, Symbol Sprite도 위에서 살펴본대로 단점이 존재했습니다.
또, Inline Svg React 에서 JSX Svg가 함께 들어가 JS 번들에 Svg가 포함된다고 합니다.
JS는 같은 용량의 다른 Asset에 비해 더 많은 비용을 드는데요.
자바스크립트의 파싱과 컴파일은 실행 직전에 일어나고 SvgJS에 포함되면 이 시간을 늘리기에 사용자가 상호작용을 기다리는 시간이 늘어날 수 있다고 합니다.
notion image
하지만 실제로 아이콘에 의해 성능 저하가 일어난적이 없어서 저에겐 피부로 와닿지는 않았습니다.
이렇듯 각 방식은 장단점이 존재하기에 언제나 자신의 프로덕트에 알맞은 기술을 때에 따라 취사 선택하고 사용할 기술을 잘 이해하고 있어야 함을 깨닫는 경험이였습니다.

정리

여기까지 각 Svg 사용 방식에 무엇이 있는지, 장단점은 어떤게 있는지 알아 보았습니다.
Gloddy에서는 아이콘이 깜빡이는 문제가 있기도 했고 Symbol Sprite의 편리함이 좋았기 때문에 페이지에 노출이 찾은 아이콘들중 일부만 Inline Svg로 변경해 혼합하여 사용하고 있습니다.
개발의 많은 부분이 그러하듯 Svg또한 프로덕트에 맞는 기술을 취사선택 해야하며, 팀원과의 의사결정을 통해 가이드라인을 제공하는 것도 매우 좋은 방법이라고 생각합니다.