SVG 잘 활용해보기

krkorklo·2022년 11월 30일
0

SVG에 대해 공부하게 된 계기

프로젝트를 진행하던 중 toolkit에서 핸드툴을 선택하는 기능이 필요했는데, 처음에는 두 개의 파일을 다운받아서 각각 사용했었다. 근데 active/inactive처럼 단순히 색상만 바뀌는 이미지들을 중복으로 저장하는 것이 비효율적이라 생각하게 됐고 SVG를 어떻게 조작해서 사용해보자,,,라고 결심하면서 공부하기 시작했다

(css로도 조금씩 할 수 있지만 SVG로 하는게 더 멋드러지다고 생각하면서 시작하는 글,,,)

SVG?

  • SVG(Scalable Vector Graphics)는 2차원 벡터 그래픽을 서술하는 XML 기반 마크업 언어

Raster

  • 이미지를 픽셀로 표현하는 방식 (비트맵)
  • 각 픽셀에 색을 입히고 픽셀을 모아서 이미지를 그림
  • 픽셀 특성상 확대를 하면 그림이 깨지는 계단식 현상이 나타남

Vector

  • 수학적 함수를 사용해서 도형과 선을 그림
  • 확대해도 깨지지 않음

SVG 구성

<svg width="200" height="250" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>

svg

  • 최상단 태그
  • version : SVG가 어떤 명세를 따르는지 표시
  • xmlns : XML 문서의 속성으로 namespace를 의미 (요소 간의 이름 충돌 방지)
  • 브라우저에서 사용하는 경우 version, xmlns 속성은 필요없음

rect

<rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>
<rect x="60" y="10" rx="10" ry="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>
  • 화면에 사각형을 그림
  • x : 좌측 상단부터 떨어진 x값
  • y : 좌측 상단부터 떨어진 y값
  • width : 사각형의 너비
  • height : 사각형의 높이
  • rx : 사각형 꼭짓점의 둥근 정도로 x 방향으로의 반지름
  • ry : 사각형 꼭짓점의 둥근 정도로 y 방향으로의 반지름
  • stroke : 선의 색상
  • stroke-width : 선의 굵기
  • fill : 사각형 내부 색상

circle

<circle cx="25" cy="75" r="20" stroke="red" fill="transparent" stroke-width="5"/>
  • cx : 원의 중심 좌표 x값
  • cy : 원의 중심 좌표 y값
  • r : 원의 반지름

ellipse

<ellipse cx="75" cy="75" rx="20" ry="5" stroke="red" fill="transparent" stroke-width="5"/>
  • cx : 타원 중심 좌표 x값
  • cy : 타원 중심 좌표 y값
  • rx : 타원의 x방향으로 반지름 길이
  • ry : 타원의 y방향으로 반지름 길이

line

<line x1="10" x2="50" y1="110" y2="150" stroke="orange" stroke-width="5"/>
  • x1 : 첫 번째 점의 x값
  • x2 : 두 번째 점의 x값
  • y1 : 첫 번째 점의 y값
  • y2 : 두 번째 점의 y값

polyline

<polyline points="60 110 65 120 70 115 75 130 80 125 85 140 90 135 95 150 100 145"
    stroke="orange" fill="transparent" stroke-width="5"/>
  • 연결된 직선들
  • points : 포인트들의 목록으로 공백, 쉼표, 줄 바꿈 문자 등으로 구분되며 각 포인트는 모두 x, y 좌표를 가진다

polygon

<polygon points="50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180"
    stroke="green" fill="transparent" stroke-width="5"/>
  • 연결된 직선들이지만 polyline과 달리 자동으로 마지막 포인트에서 첫 번째 포인트를 이어 닫힌 모양을 생성

path

<path d="M20,230 Q40,205 50,230 T90,230" fill="none" stroke="blue" stroke-width="5"/>
  • d : 그리기 위한 경로

  • 선 명령어

    • M(Move to) : path가 맨 처음 그리기를 시작하는 좌표 x, y (소문자 m은 뒤에 상대적 단위)
    • L(Line to) : 현재 위치에서 새 위치 x, y로 선을 그음 (소문자 l은 뒤에 상대적 단위)
    • H : L 명령어에서 가로선을 그리는 축약형 명령어로 x를 입력받음 (소문자 h은 뒤에 상대적 단위)
    • V : L 명령어에서 세로선을 그리는 축약형 명령어로 y를 입력받음 (소문자 v은 뒤에 상대적 단위)
    • Z(Close path) : 현 위치에서 시작점으로 직선을 그리고 path를 끝냄 (꼭 쓰지 않아도 됨)
      <path d="M10 10 h 80 v 80 h -80 Z" fill="transparent" stroke="black"/>
      • 어떤 도형이 나올까?
        짜잔
  • 곡선 명령어

    • C : 곡선을 생성하는 명령어

      // C x1 y1, x2 y2, x y (c dx1 dy1, dx2 dy2, dx dy)
      
      <path d="M10 10 C 20 20, 40 20, 50 10" stroke="black" fill="transparent"/>
      • x, y : 곡선의 끝점
      • xn, yn : 제어점으로 시작점과 끝 점에서 곡선의 방향을 기술하며 각 제어점의 방향으로 부드러운 곡선을 생성
    • S : 한 선의 제어점을 다른 제어점과 대칭으로 완만한 경사를 가지는 곡선을 생성하는 명령어

      // S x2 y2, x y (s dx2 dy2, dx dy)
      
      <path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80" stroke="black" fill="transparent"/>

      • 첫 번째 제어점은 이전에 사용한 제어점을 뒤집은 것으로 간주
    • Q : 하나의 제어점이 시작점과 끝점의 방향을 결정

      // Q x1 y1, x y (q dx1 dy1, dx dy)
      
      <path d="M20 50 Q60 75, 100 50" fill="none" stroke="black" />

이론 공부가 생각보다 매우 길어진 것 같지만,, 이를 바탕으로 SVG 커스텀을 해볼 수 있다.

SVG를 커스텀해서 효율적으로 사용해보자

기존 사용 방식

// inactive
<svg width="44" height="40" viewBox="0 0 44 40" fill="none" xmlns="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" clip-rule="evenodd" d="M20.3953 11C20.3953 9.89543 21.2907 9 22.3953 9C23.2677 9 24.0096 9.55857 24.283 10.3376C24.601 10.1244 24.9836 10 25.3953 10C26.4998 10 27.3953 10.8954 27.3953 12V13.2676C27.6894 13.0974 28.031 13 28.3953 13C29.4998 13 30.3953 13.8954 30.3953 15L30.3953 23C30.3953 26.3137 27.709 29 24.3953 29H23.3953C21.5653 29 19.9262 28.1797 18.8271 26.8902C18.7996 26.8662 18.7724 26.8416 18.7455 26.8164L13.7406 22.1104C12.839 21.2628 12.7481 19.8444 13.5367 18.8895C14.309 17.9542 15.6644 17.7838 16.6422 18.5062L17.3953 19.0626V13C17.3953 11.8954 18.2907 11 19.3953 11C19.7596 11 20.1011 11.0974 20.3953 11.2676V11ZM21.3953 11C21.3953 10.4477 21.843 10 22.3953 10C22.9476 10 23.3953 10.4477 23.3953 11V18H24.3953V12C24.3953 11.9993 24.3953 11.9987 24.3953 11.998C24.3963 11.4466 24.8436 11 25.3953 11C25.9475 11 26.3953 11.4477 26.3953 12V18H27.3953V15C27.3953 14.4477 27.843 14 28.3953 14C28.9475 14 29.3953 14.4477 29.3953 15L29.3953 23C29.3953 25.7614 27.1567 28 24.3953 28H23.3953C21.842 28 20.4541 27.2917 19.5371 26.1805C19.5009 26.1514 19.4653 26.1205 19.4305 26.0878L14.4256 21.3819C13.9076 20.8949 13.8555 20.0739 14.3077 19.5262C14.7432 18.9989 15.4993 18.9052 16.0479 19.3105L18.3953 21.0448V13C18.3953 12.4477 18.843 12 19.3953 12C19.9475 12 20.3953 12.4477 20.3953 13V18H21.3953V11Z" fill="black">
	</path>
</svg>

// active
<svg width="44" height="40" viewBox="0 0 44 40" fill="none" xmlns="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" clip-rule="evenodd" d="M20.3953 11C20.3953 9.89543 21.2907 9 22.3953 9C23.2677 9 24.0096 9.55857 24.283 10.3376C24.601 10.1244 24.9836 10 25.3953 10C26.4998 10 27.3953 10.8954 27.3953 12V13.2676C27.6894 13.0974 28.031 13 28.3953 13C29.4998 13 30.3953 13.8954 30.3953 15L30.3953 23C30.3953 26.3137 27.709 29 24.3953 29H23.3953C21.5653 29 19.9262 28.1797 18.8271 26.8902C18.7996 26.8662 18.7724 26.8416 18.7455 26.8164L13.7406 22.1104C12.839 21.2628 12.7481 19.8444 13.5367 18.8895C14.309 17.9542 15.6644 17.7838 16.6422 18.5062L17.3953 19.0626V13C17.3953 11.8954 18.2907 11 19.3953 11C19.7596 11 20.1011 11.0974 20.3953 11.2676V11ZM21.3953 11C21.3953 10.4477 21.843 10 22.3953 10C22.9476 10 23.3953 10.4477 23.3953 11V18H24.3953V12C24.3953 11.9993 24.3953 11.9987 24.3953 11.998C24.3963 11.4466 24.8436 11 25.3953 11C25.9475 11 26.3953 11.4477 26.3953 12V18H27.3953V15C27.3953 14.4477 27.843 14 28.3953 14C28.9475 14 29.3953 14.4477 29.3953 15L29.3953 23C29.3953 25.7614 27.1567 28 24.3953 28H23.3953C21.842 28 20.4541 27.2917 19.5371 26.1805C19.5009 26.1514 19.4653 26.1205 19.4305 26.0878L14.4256 21.3819C13.9076 20.8949 13.8555 20.0739 14.3077 19.5262C14.7432 18.9989 15.4993 18.9052 16.0479 19.3105L18.3953 21.0448V13C18.3953 12.4477 18.843 12 19.3953 12C19.9475 12 20.3953 12.4477 20.3953 13V18H21.3953V11Z" fill="white">
	</path>
</svg>
  • fill 색상만 다른 두 개의 파일
  • 불필요한 파일 생성 → icon이 추가될 때마다 2배로 늘어남!

SVG를 커스텀하는 방식

<svg width="44" height="40" viewBox="0 0 44 40" fill="none" xmlns="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" clip-rule="evenodd" d="M20.3953 11C20.3953 9.89543 21.2907 9 22.3953 9C23.2677 9 24.0096 9.55857 24.283 10.3376C24.601 10.1244 24.9836 10 25.3953 10C26.4998 10 27.3953 10.8954 27.3953 12V13.2676C27.6894 13.0974 28.031 13 28.3953 13C29.4998 13 30.3953 13.8954 30.3953 15L30.3953 23C30.3953 26.3137 27.709 29 24.3953 29H23.3953C21.5653 29 19.9262 28.1797 18.8271 26.8902C18.7996 26.8662 18.7724 26.8416 18.7455 26.8164L13.7406 22.1104C12.839 21.2628 12.7481 19.8444 13.5367 18.8895C14.309 17.9542 15.6644 17.7838 16.6422 18.5062L17.3953 19.0626V13C17.3953 11.8954 18.2907 11 19.3953 11C19.7596 11 20.1011 11.0974 20.3953 11.2676V11ZM21.3953 11C21.3953 10.4477 21.843 10 22.3953 10C22.9476 10 23.3953 10.4477 23.3953 11V18H24.3953V12C24.3953 11.9993 24.3953 11.9987 24.3953 11.998C24.3963 11.4466 24.8436 11 25.3953 11C25.9475 11 26.3953 11.4477 26.3953 12V18H27.3953V15C27.3953 14.4477 27.843 14 28.3953 14C28.9475 14 29.3953 14.4477 29.3953 15L29.3953 23C29.3953 25.7614 27.1567 28 24.3953 28H23.3953C21.842 28 20.4541 27.2917 19.5371 26.1805C19.5009 26.1514 19.4653 26.1205 19.4305 26.0878L14.4256 21.3819C13.9076 20.8949 13.8555 20.0739 14.3077 19.5262C14.7432 18.9989 15.4993 18.9052 16.0479 19.3105L18.3953 21.0448V13C18.3953 12.4477 18.843 12 19.3953 12C19.9475 12 20.3953 12.4477 20.3953 13V18H21.3953V11Z" fill="current">
	</path>
</svg>
  • 속성값을 current로 작성하거나 비우면 컴포넌트로 불러오는 SVG에 props로 넘겨준 값이 들어감!
import { ReactComponent as SelectCursor } from '@assets/icon/toolkit-select-cursor.svg';

<SelectCursor fill={cursor === toolItems.SELECT ? 'white' : 'black'} /> 
  • props로 fill 속성을 넘겨주면 원하는 값이 들어감!

컴포넌트 생성 방식

// dropdown inactive
<svg width="16" height="9" viewBox="0 0 16 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="1" y1="1" x2="8" y2="8" stroke="black" stroke-linecap="round" />
<line x1="8" y1="8" x2="15" y2="1" stroke="black" stroke-linecap="round" />
</svg>

// dropdown active
<svg width="16" height="9" viewBox="0 0 16 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="1" y1="8" x2="8" y2="1" stroke="black" stroke-linecap="round"/>
<line x1="8" y1="1" x2="15" y2="8" stroke="black" stroke-linecap="round"/>
</svg>
  • line 각각 y1, y2의 수정이 필요 → 중복된 props가 발생
  • current로 해결할 수 없는 문제 → 그냥 컴포넌트를 만들자!
function DropdownIcon({ firstY1, firstY2 }: DropdownIconProps) {
	return (
		<svg width="16" height="9" viewBox="0 0 16 9" fill="none" xmlns="http://www.w3.org/2000/svg">
			<line x1="1" y1={firstY1} x2="8" y2={firstY2} stroke="black" strokeLinecap="round" />
			<line x1="8" y1={firstY2} x2="15" y2={firstY1} stroke="black" strokeLinecap="round" />
		</svg>
	);
}

export default DropdownIcon;

<DropdownIcon firstY1={1} firstY2={8} />

참고자료
https://developer.mozilla.org/ko/docs/Web/SVG/Tutorial/Paths
https://a11y.gitbook.io/graphics-aria/svg-graphics/svg-paths-shape

0개의 댓글