React에서 SVG 이미지를 사용하기

FGPRJS·2021년 11월 26일
4

SVG이미지의 특징은 xml로 되어있어, 각 요소들에 대한 접근이 가능하다는 것이다.
일반적인 비트맵이미지로는 수행할 수 없는 엘리먼트 단위의 제어등이 가능하다. (특정 Path의 색상만을 바꾼다던지, 크기를 변경한다던지, 보이지 않게 한다던지 등..)
여러가지 프레임워크들이 이 SVG이미지를 지원하며, 여러 방법으로 제어할 수 있게 되어있다.

이 글은 JS에서 제공하는 SVG처리방법, 그리고 React에서 그것을 사용하는 방법에 대한 기록이다. 특히, svg의 엘리먼트에 접근하는 것에 중점을 둔다.

사용하는 샘플 이미지


목표

SVG파일 하나만으로 여러 작업을 수행할 수 있도록, SVG파일 내의 엘리먼트등을 자유롭게 조작할 수 있게 하는 것.

연습을 위한 목표 사양

  • SVG의 한 엘리먼트 위에 마우스를 올리면(hover) 색을 바꾸는 애니메이션(transition),
  • 그리고 마우스를 올리지 않으면(leave) 색이 돌아오는 애니메이션

결론

  • ReactSVG 모듈을 통하여 SVGDOM에 로드가 완료되었을때의 시점에 접근을 할 수 있다.
  • ReactSVG 컴포넌트의 afterInjection 어트리뷰트의 svgDOM에 로드가 완료된 SVGSVGSVGElement타입이며, 이 것을 이용하여 Element에 접근할 수 있다.
  • SVGSVGElementclassList에 추가되는 문자열은 CSS에서 접근할 수 있는 class가 된다.

SVG 단독으로 사용하기

How to import a svg file in javascript
위 링크에서 말하는 내용은 다음과 같다.

  • img 태그로 가져올 수 있다.
    매우 간편하지만, 이 경우 SVG파일의 엘리먼트 조작이 안된다. 즉, SVG이미지를 사용하는 의미가 퇴색된다.

  • object 태그로 가져올 수 있다.
    typeimage/svg+xml로 하여 가져올 수 있다.
    이 방법만이 위 사이트에서 제시하는 유일한 엘리먼트가 조작 가능한 SVG import 타입이다.

  • iframe 태그로 가져올 수 있다.
    하지만 이 방법은 SVG의 엘리먼트를 가져올수도, 성능도 나빠 위 사이트에서는 전혀 추천하지 않는 방법이다.


object 태그를 사용한 svg이미지 가져오기

하기 코드는 위 링크에 기재된 예시 코드이다.
다음으로 구분되어있다.

  • type
    • 해당 태그가 무슨 object인지 타입을 정한다.
  • data
    • img의 src의 그것과 하는 역할이 유사하다.
  • class
    • 기존 태그의 class의 그것이다.
<!DOCTYPE html>
<html lang="en">
<body>
<object type="image/svg+xml"
		data=
"https://media.geeksforgeeks.org/wp-content/cdn-uploads/20210205161739/Screenshot-2021-02-05-161721.png"
		class="logo">
	GFG Logo
</object>

</body>

</html>

이를 예시로서, 다음과 같이 구현하였다.

import kr_map from './kr.svg'
import React from 'react'

export default class map extends React.Component {
    render(){
        return <object 
          type = "image/svg+xml" 
          className = "main_map" 
          data = {kr_map}
      	></object>
    }
}

해당 코드는 동작한다.
CSS가 조금 다르게 동작하지만, 동작 자체는 확인하였다.

img와 class는 다음과 같은 CSS환경에서 다르게 동작한다.

.main_map {
  width: 100%;
  height: auto;
}

  • img 태그에서 의도한 그대로 동작하는 모습
    • img의 너비는 브라우저 너비와 같으며, 높이는 너비에 따른다.

  • object 태그에서 조금 이상하게 동작하는 모습
    • 분명히 작아지기는 하는데, 원하는 대로 동작하지는 않음
    • 하기 [SVG 엘리먼트에 CSS 스타일을 적용하기] 항목처럼 수정을 시도함

React환경에서 object로 가져온 svg의 element에 접근하기

다음과 같은 방식으로 가져올 수 있다. 상기 링크를 참조하여 엘리먼트를 조작할 수 있게 코드를 바꾸어본다.

object 안의 무언가를 엘리먼트로 빼올 수 있을지 판단하기 위해 다음과 같은 코드를 작성하였다.

import kr_map from './kr.svg'
import React from 'react'

export default class map extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            map_img : <object type = "image/svg+xml" className = "main_map" data = {kr_map}></object>
        }
        console.log(this.state.map_img)
    }

    render(){
       return this.state.map_img;
    }
}
[HMR] Waiting for update signal from WDS...

오류가 발생한다.
오류 해결 방법

오류를 해결하니 다음과 같은 결과를 준다.

단순한 React Element이며, SVG의 엘리먼트는 딱히 뺄 수 있을 것 같아보이지 않는다.

당연한 바이지만, React에서는 render()를 호출하기 전에는 DOM에 추가하지 않기 때문에, 다음과 같은 코드로는 목적을 달성할 수 없다.

constructor(props){
        super(props);
        this.state = {
            map_img : <object type = "image/svg+xml" className = "main_map" data = {kr_map}></object>
        }
       

	//render()를 호출한 이후에 DOM에 추가되는데,
	//constructor에서 호출하면 아무것도 불러올 수 없다.
	console.log(document.getElementById("KOR2494"));
    }

상기 기재된 방식은 SVG이미지가 object로 추가된 이후에 접근할 수 있는 방식인데, React특성상 render()가 호출이 되어야만 해당 컴포넌트DOM에 추가된다.

그리고 사실 render()를 호출한 직후에 컴포넌트DOM에 추가되는 것도 아니다. (여러 컴포넌트들의 render()를 모아서 한번에 추가함)

따라서 상기 방법으로 해결될 것으로 보이지는 않는다.


React SVG 모듈을 사용하기

직접 수행해보려고 하였으나, 상기 작업으로 수행하는 것은 굉장히 번거로운것을 떠나 실행 가능성조차 희박하므로, 최적화된 라이브러리를 사용해서 원하는 작업을 수행하려고 한다.

이 모듈은 방금 상기 시도에서 좌절되었던 SVG가 DOM에 추가되기 전 / 추가된 후에 작업을 정해놓고, 수행할 수 있게 한다.

상기 링크의 설명서를 참고하여, 다음과 같이 작성하였다.

import kr_map from './kr.svg'
import React from 'react'
import { ReactSVG } from 'react-svg'

export default class map extends React.Component {
    render(){
       return <ReactSVG
            afterInjection = {(error, svg) => {
                if (error) {
                    console.error(error);
                    return;
                }
                console.log(svg);
            }
            }
            src = {kr_map}
       ></ReactSVG>
    }
}

console.log(svg)를 통하여, svg는 완벽하게 svg를 읽은 결과가 된다!
svg의 타입은 SVGSVGElement 라고 한다.

이 svg의 자식 element를 갖고 오려면

  • .childnodes (type : NodeListOf< ChildNode >)
  • .children (type : HTMLCollection)
    등을 사용할 수 있다.

SVG 엘리먼트에 CSS 스타일을 적용하기

다음 링크에서는 어떻게 SVG엘리먼트에 CSS스타일을 적용시킬 수 있는지 기재되어있다.
다음 링크를 통하여 이벤트를 어떻게 넣는지 알 수 있다.

관련하여, 다음과 같이 코드를 작성하였다.

import kr_map from './kr.svg'
import React from 'react'
import { ReactSVG } from 'react-svg'

export default class map extends React.Component {
    render(){
       return <ReactSVG
            afterInjection = {(error, svg) => {
                if (error) {
                    console.error(error);
                    return;
                }

                svg.classList.add('region');
            }
            }
            src = {kr_map}
       ></ReactSVG>
    }
}

이 작업으로 인하여, svgclassregion이 되었다.
classList에 부여한 class이름은 CSS에서 적용된다.

svg파일은 다음과 같이 수정하였다.

<svg baseprofile="tiny" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.2" xmlns="http://www.w3.org/2000/svg">
 <path d="M372.9 ... 이후 생략"/>

주요 변경점

  • svg에 있던 fill은 제거하였다. (css 적용을 위하여)
  • viewbox및 width, height는 현재는 제거

CSS파일은 다음과 같이 수정하였다.

.region {
  position:relative;

  fill:blueviolet;

  transition-duration: .2s;

  z-index: 0;
}

.region :hover{
  fill:crimson;

  transition-duration: .2s;

  z-index: 1;
}

주요 변경점

  • 색의 구분을 위해 임의의 색상 부여
  • 애니메이션 부여

SVG의 ViewBox와 ViewPort

상기의 의도한 CSS설정과 완전히 다르게 동작하는 SVG에 대한 항목이다.

다음 링크를 통해서, SVG파일의 ViewBox와 ViewPort에 대해 알 수 있다.

주요 요점은 다음과 같다.

  • ViewBox는 SVG가 보일 영역을 지정할 수 있게 한다.
  • ViewPortViewBox와 비슷하지만, PanZoom이 가능하다는 특징이 있다.

Pan과 Zoom의 기능은 뒤로 하고, 현재는 SVG의 영역을 지정해주는것에만 집중한다.

  • ViewBox를 시도하는 경우, 다음과 같이 수정한다.
<svg viewBox = "0 0 1000 1300" 이하생략 >
  <path>...</path>
</svg>

결과

  • 지원함.
    윈도우 사이즈에 영향을 받음

  • ViewPort를 시도하는 경우, 다음과 같이 수정한다.

<svg width = "1000" height = "1300" 이하생략 >
  <path>...</path>
</svg>

결과

  • 지원하지 않음.
    고유의 크기를 가지며 윈도우 사이즈에 아무 영향도 받지 않음
  • ViewBoxViewPort를 혼용하는 것으로 Zoom등이 가능하게 된다고 기재되어있다.
    이 기능을 추후에 확장할 수 있도록, svg를 수정하는 것이 아니라, React Component를 수정하는 방식으로 진행한다.
import kr_map from './kr.svg'
import React from 'react'
import { ReactSVG } from 'react-svg'

export default class map extends React.Component {
    constructor(props){
        super(props);
    }

    render(){
       return <ReactSVG
            afterInjection = {(error, svg) => {
                if (error) {
                    console.error(error);
                    return;
                }

                svg.setAttribute('width','1000');
                svg.setAttribute('height','1300');
                svg.setAttribute('viewBox','0 0 1000 1300');
                svg.setAttribute('preserveAspectRatio',"xMinYMin meet");

                svg.classList.add('region');
            }
            }
            src = {kr_map}
       ></ReactSVG>
    }
}
  • 추후에 viewBox등을 객체화 하여야 한다.

번외

  • SVG xml코드를 통째로 가져오는 방법도 존재한다고 한다.

  • 그 외에도 여러가지 모듈이 있는데,

  • 이 중에서는 SVG 엘리먼트를 각기 다른 컴포넌트로 취급할 수 있는 패키지도 있다고 한다.

profile
FGPRJS

0개의 댓글