develop

프론트엔드가 이미지 색상을 실시간으로 바꾸는 법 - svg 시도

crab. 2023. 1. 20. 16:11
 

구현해야 하는 목표

  • 보기와 같이 유저가 파레트를 이용하여 색상을 자유롭게 고르면
  • 그 색상이 우리의 사진에 실시간으로(!) 적용되어 미리보기를 할 수 있어야 했다.

구상

  • 처음 기획을 받았을때는 솔직하게 “이게 말이돼?” 라는 생각과 함께
  • 분명 다른 방법이 있을꺼야 서버를 경유한다거나 특정 전용 라이브러리가 있다거나 하는 생각을 했다.
  • 하지만 마음 한편에는 구현할 수 있으면 진짜 대박이겠다 라는 생각도 했다.
  • 그런 와중에 주위의 다른 기획자분께서 cto님께서 이 부분을 진행중에 있고 코드로 구현한다는 말을 들었다.
  • 그때 머릿속을 딱 지나가는 게 아! svg는 vscode상에서 코드로 볼 수 있으니까 거기서 색상 관련된부분을 css in js 로 제어할 수 있다면 불가능한 일은 아니다! 라는 생각이 들었다.
  • 그리고 그렇게 거대한 삽질이 시작되었다..

svg시도

  • 삽질이 으레 그렇듯 나는 판을 벌리기 시작했다.
  • 우선 svg로 이미지를 가져왔던 레퍼런스를 찾았고 ← 놀랍게도 금방 찾았다.
  • 심지어 그 레퍼런스에서의 이미지가 코드로 변경되는 것도 확인했다.
  • 그 방법은

SVG 이미지의 색상 변경

SVG 이미지는 아래와 같이 xml 코드로 이루어져있다.

<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 20L12 4" stroke="#0A0400" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 9.5L11.6464 3.85355C11.8417 3.65829 12.1583 3.65829 12.3536 3.85355L18 9.5" stroke="#0A0400" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>

여기서 SVG의 색상을 의미하는 속성값들은 다음과 같다.

  • stroke : 보통 선의 색상을 의미함
  • fill : 보통 어떤 영역 안의 색상을 의미하며 background-color 와 유사함

변경하는 방법은 대표적으로 아래와 같이 세 개가 있다.

current 또는 stroke, fillcurrentColorSVG를 반환하는 컴포넌트를 만들어 props로 색상 값 받기

1. current 또는 stroke, fill

변경하고 싶은 값을 아래와 같이 current 로 지정해주면 CSS로 색상 값을 변경할 수 있다.

위의 이미지에서 stroke="black" -> stroke="current" 로 변경해주었다.

<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 20L12 4" stroke="current" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 9.5L11.6464 3.85355C11.8417 3.65829 12.1583 3.65829 12.3536 3.85355L18 9.5" stroke="current" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>

또는 stroke 는 stroke="stroke" 로, fill="fill" 로 설정해주어도 된다.

리액트를 사용하면 SVG파일을 컴포넌트로 import할 수 있다.가져온 SVG 컴포넌트에게 CSS 스타일을 아래와 같이 넣어주면 해당 값을 변경할 수 있다.

import { ReactComponent as Arrow } from './arrow.svg';

// ...
<Arrow
	css={{
         stroke: 'red', // 라인 색상 변경
         fill: 'blue', // 배경 색상 변경
         width: '20px'
         height: 'auto',
        }}
  />

하지만 이 방법은 문제가 존재한다. SVG 이미지가 원하지 않는 부분에 stroke, fill이 적용될 수 있기 때문이다. 따라서 보통 두번째 방법을 사용한다.

2. currentColor

위의 방법과 거의 유사하지만, current 대신에 currentColor 을 변경하고 싶은 구간에 넣어준다.

<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 20L12 4" stroke="currentColo" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 9.5L11.6464 3.85355C11.8417 3.65829 12.1583 3.65829 12.3536 3.85355L18 9.5" stroke="currentColo" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>

currentColor 는 css로 color 값을 설정 했을 경우 그 색상 값이 적용된다.

import { ReactComponent as Arrow } from './arrow.svg';

// ...
<Arrow
	css={{
         color: 'red', // currentColor을 넣은 구간의 색상 변경
         width: '20px'
         height: 'auto',
        }}
  />

이 방법의 장점은 원치 않는 stroke와 fill이 적용되는 것을 막을 수 있다는 점이다. 하지만 color 로 변경한 단 한가지 색상만 수정할 수 있기 때문에 제약이 있다.

3. SVG를 반환하는 컴포넌트를 만들어 props로 색상 값 받기

이 방법은 위의 두 개의 방법과는 다르게 SVG를 반환하는 컴포넌트를 만든다.

리액트를 사용하는 개발자들은 아래의 코드를 보면 한번에 이해가 될 것이다.

주의할 점은 JSX 는 props를 반드시 camel case 로 작성해주어야 한다. 따라서 stroke-width -> strokeWidth 처럼 네임을 변경해 주었다.

import { ReactElement } from 'react';

interface ArrowProps {
  stroke: string;
  fill: string;
}

export default function Arrow({ stroke, fill }: ArrowProps): ReactElement {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M12 20L12 4"
        stroke={stroke}
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M6 9.5L11.6464 3.85355C11.8417 3.65829 12.1583 3.65829 12.3536 3.85355L18 9.5"
        stroke={stroke}
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

이 방법은 SVG 이미지 마다 컴포넌트를 하나하나 만들어 주어야 해서, SVG 이미지가 엄청나게 많다면 상당히 번거로운 작업이 될 수 있다. 따라서 두 번째 방법을 사용하는 것을 추천한다.

나의 경우

  • 나의 이미지는 위의 방법들은 내가 잘 못해서인지 잘 먹히지 않았고
  • 나는 svg의 path태그마다 stroke값과 fill값을 직접 넣어주고 변경해주는 방식으로 이미지를 바꿔줬다.
import { ReactComponent as Symbol } from "../images/symbolRed.svg";
  • 가져올때는 이렇게 가져왔으며
<StyledSymbol onClick={onClickSimbol} />
const StyledSymbol = styled(Symbol)`
  cursor: pointer;
  user-select: none;
  height: 37px;
  margin-right: 30px;

  @media screen and (max-width: ${mobile}) {
    display: none;
  }
`;
  • 사용할때는 이렇게 사용했다.

svg시도 이후

  • 이 방법을 사용해서 실제로 레퍼런스 사이트의 이미지의 색상은 잘 변경했다.
  • 또한 우리 서비스의 테두리 색상도 잘 변경되었다.
  • 하지만 중요한 영역내의 색상이 변경되지 않았다..
  • 이 부분부터 나의 삽질이 시작되었다.

무엇이 문제였을까?

  • 나는 이때부터 svg에 집착하며, 본질을 흐려가며 흐린눈으로 문제를 바라봤다.
  • 내가 해결해야할 문제는 정해진 사진이 그 사진의 모든 채도와 명함의 비율을 유지한 채로 색상이 변경되어야 한다.
  • 하지만 이 svg방식은 우선 하나의 색도 변경하지 못했을 뿐더러 변경이 된다 해도 사진마다 일일이 속성을 주며 일일이 다 작업을 해야한다.(색상 범위의 수만큼!!!)
    <svg width="86" height="39" viewBox="0 0 86 39" fill="none" xmlns="http://www.w3.org/2000/svg">
    <line x1="8.74849" y1="2.05293" x2="11.0003" y2="9.25869" stroke="#E25B45" stroke-width="3.20895" stroke-linecap="round"/>
    <line x1="15.8081" y1="2.05293" x2="18.0599" y2="9.25869" stroke="#E25B45" stroke-width="3.20895" stroke-linecap="round"/>
    <path d="M70.6355 6.14842C65.8894 5.95267 61.5766 7.82028 58.5217 10.9265C57.3953 12.0721 55.5695 12.085 54.4431 10.9362C51.5326 7.97431 47.4829 6.13558 43 6.13558C38.5171 6.13558 34.4674 7.97431 31.5569 10.9394C30.4305 12.0882 28.6079 12.0753 27.4783 10.9297C24.4234 7.82349 20.1106 5.95267 15.3645 6.15163C6.85119 6.5014 0.0257582 13.6092 8.66474e-05 22.129C-0.0287939 31.0146 7.16567 38.2251 16.0448 38.2251C20.5277 38.2251 24.5806 36.3863 27.4879 33.4213C28.6111 32.2789 30.4305 32.2789 31.5537 33.4213C34.4642 36.3863 38.5171 38.2251 43 38.2251C47.4829 38.2251 51.5358 36.3863 54.4431 33.4213C55.5662 32.2789 57.3857 32.2789 58.5088 33.4213C61.4194 36.3863 65.4723 38.2251 69.9552 38.2251C78.8343 38.2251 86.0288 31.0146 85.9999 22.129C85.9742 13.6092 79.1488 6.5014 70.6355 6.14842Z" fill="#E25B45"/>
    <path d="M12.726 25.71H17.5395" stroke="white" stroke-width="1.28358" stroke-miterlimit="10" stroke-linecap="round"/>
    <path d="M8.22384 20.9257L10.6947 20.255" stroke="white" stroke-width="1.28358" stroke-miterlimit="10" stroke-linecap="round"/>
    <path d="M22.0417 20.9258L19.5708 20.2551" stroke="white" stroke-width="1.28358" stroke-miterlimit="10" stroke-linecap="round"/>
    </svg>
    
    // 이런 svg파일 있다면 저 path 하나당 하나씩 다 stroke값과 fill값을
    // 조절해줘야 한다..
  • 흐린 눈의 문제점은 놀랍게도 자기 자신은 흐린눈을 하고 있다고 생각하지 않는 것이며 마음 한켠에 이러면 안된다는 걸 알면서도 이미 노력한 기회비용과 스트레스때문에 해결방향을 바꾸기 쉽지 않다는 것이다.
  • 나는 이 문제를 선임 개발자 분의 조언을 듣고서야 겨우 깨닫고 방향을 바꿀 수 있었다..
  • 눈앞에서 직접 색깔이 한가지만 바뀌는 걸 보여주시는데 그제서야 잘못된 방향으로 노력하고 있었구나를 깨달았다..
    • 정확히는 해결할 수는 있지만 노력이 너무 많이 들어가 의미가 없는 방향의 해결법이었다.
  • 선임 개발자님께서는 또 두 개의 이미지를 겹쳐서 하나의 이미지에 투명도를 주고 다른 이미지의 색상을 변경시키면 자연스럽게 색상을 변경시킬 수 있지 않겠냐고 하셨다.
    • 진짜 이거 쓰면서 다시 느끼는건데 해결법을 믹서기로 갈아서 비행기 모형 숟가락으로 직접 “슈웅~ 해결법 들어갑니다~”하시면서 떠먹여준 수준이다..
  • (추가로 전에 박사님께서 이미 슬랙으로 태그해주신 스레드에 해결방법의 힌트가 고스란히 정리되어 있었다..)
  • (전에 ftp사태때도 그렇고 이번에도 그렇고 개발자는 소통과 유연한 사고, 그리고 넓은 시각과 눈치가 복합적으로 필요하다.. ‘나는 초보니까 항상 다른 개발자분들의 말이 옳다 일단 전부..’ 이렇게 생각하고 수용해야한다.)
  • (ftp사태를 겪고도 티끌만큼 성장?했나..? 의문이 드는 순간이었다.)
    • (그래도 전보다는 재빠르게 수용했다..아마도..)

출처

https://velog.io/@heelieben/React-Icon-컴포넌트-만들기-with-SVG-stroke-이미지컴포넌트-동적으로-가져오기

https://velog.io/@rootcho/React에서-svg파일-사용하는-법

https://github.com/kristerkari/react-native-svg-transformer/issues/105

https://code-masterjung.tistory.com/108

https://nykim.work/35

https://kyounghwan01.github.io/blog/React/handling-svg/#next에서-svg-사용하기