카테고리 없음

svg로 변수에 따라 변경되는 이미지 만들기

Honey-dev 2023. 4. 27. 16:52

배경

개발자 지망생과 주니어 개발자를 위한 프로젝트 공유 및 코드 리뷰 사이트를 만드는 프로젝트를 진행중이다.

이 서비스를 이용하면서 프로젝트를 공유하여 피드백을 받고, 그를 반영하여 리팩토링한 결과를 다시 공유하는 방식으로 프로젝트 디벨롭 과정을 포트폴리오화할 수 있도록 기획하였다.

코드 리뷰 또한 마찬가지로, 코드 리뷰를 반영하여 어떻게 개선하였는지 버전 관리 기능을 통해 기록할 수 있도록 하였다.

 

그러다보니 프로젝트 리팩토링 수, 코드를 리뷰한 수, 코드 리뷰가 채택되어 타인의 코드에 반영된 수 등을 스탯 카드로 제공하고 싶어졌다. 참고한 것은 깃허브 스탯을 제공하는 https://github.com/anuraghazra/github-readme-stats 이다.

github stat card

작업하다보니 api를 기다리다가 시간이 좀 남아서 이 작업을 먼저 진행하게 되었다.

 

작업과정

github stat card 레포를 포크해서 내부를 살펴보니 여러가지 테마 적용과 다이나믹한 옵션들을 제외하면 기본 구조는 변수를 적용해서 svg화 해서 이미지로 내보내는 형식이었다. 처음에는 스탯 카드를 제공해주기 위한 작은 서비스를 별도로 만들어서 유저 정보를 우리 백엔드로부터 받아오는 방식으로 구현하려고 했는데, 굳이 저 기능 하나를 위해 서버를 하나 더 띄울 필요가 있을까 팀원들과 이야기를 나눈 끝에 api로 처리하기로 하였다.

즉 변수가 적용된 svg가 나오도록 구현한 뒤 해당 svg를 string으로 우리 백엔드에 저장해 두고, 유저 id를 통해 db에서 가져온 정보를 입력한 svg string을 response하도록 결정하였다. 결론적으로 svg를 만들어야 했다.

작업중 화면

결론적으로, 이렇게저렇게 시도해보다가 필요한 svg 하위 태그들을 하나씩 생성해주었다.

 

1. svg의 viewBox 속성은 svg 벡터를 그릴 영역을 좌표로 생각했을 때 x시작점, y시작점, x시작점으로부터의 x축방향 길이, y시작점으로부터의 y축방향 길이이다. (참고: https://tecoble.techcourse.co.kr/post/2021-10-24-svg-viewBox/)

2. <text>태그의 x 속성 및 y속성은 viewBox로 지정한 영역의 원점(x시작점, y시작점)으로부터의 거리로, text의 좌하단 끝을 해당 위치로 지정해준다.

3. 따라서 위 이미지(작업중 화면)은 <svg>태그 안에 <rect>(사각형) 태그 2개를 위치 지정하여 넣고 <text>태그를 필요한 위치 지정하여 넣어서 만들었다.

4. 이때 변수를 넣어주기 위해(숫자 및 닉네임 동적 반영) encodeHTML 함수에 변수를 넣어 string을 HTML으로 인코딩하여 넣어 준다. encodeHTML 함수는 github stat card 레포에서 사용한 함수를 사용했는데, 정규표현식을 이용하여 유니코드로 변환한다.

/**
 * Encode string as HTML.
 *
 * @see https://stackoverflow.com/a/48073476/10629172
 *
 * @param {string} str String to encode.
 * @returns {string} Encoded string.
 */
const encodeHTML = (str: string) => {
  return str
    .replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
      return "&#" + i.charCodeAt(0) + ";";
    })
    .replace(/\u0008/gim, "");
};

5. 만든 svg string을 서비스 화면에 렌더링하기 위해 <img> 태그의 src 속성에서 타입을 svg+xml로 지정해주고 `<svg>...</svg>` 형식의 string을 넣어주었다. (참고: https://stackoverflow.com/questions/44900569/turning-an-svg-string-into-an-image-in-a-react-component)

        <img
          src={`data:image/svg+xml;utf8,${encodeURIComponent(StatCard3())}`}
          alt="stat-card"
          width="70%"
        />

 

기본은 이게 다고, 여기서 디자인에 따라서 더 예쁘게 바꿔주면 된다. 최종적으로는 완성된 svg string을 백엔드 코드에 넣어서 특정 api 요청시 해당 string을 응답하도록 하여 markdown image 경로에 넣을 수 있도록 구현할 예정이다.

 

 

 

 

 

 

*참고: svg string 관련 코드는 다음과 같은 형식이 된다. (string을 리턴함)

import { encodeHTML } from "./utils";

export const StatCard3 = () => {
  // 더미데이터
  const name = "개발새발";
  const title = encodeHTML(`${name}의 스탯카드!`);
  const feedbackCnt = encodeHTML(`${3}`);
  const codeReviewCnt = encodeHTML(`${3}`);
  const includedFeedbackCnt = encodeHTML(`${3}`);
  const includedCodeReviewCnt = encodeHTML(`${3}`);
  const projectRefactorCnt = encodeHTML(`${3}`);
  const codeRefactorCnt = encodeHTML(`${3}`);

  return `
    <svg viewBox="0 0 1030 445" xmlns="http://www.w3.org/2000/svg">
    <style>
      .title {
        font: 800 30px 'Segoe UI', Ubuntu, Sans-Serif;
        fill: #319795;
        animation: fadeInAnimation 0.8s ease-in-out forwards;
      }
      .content {
        font: 800 20px 'Segoe UI', Ubuntu, Sans-Serif;
        fill: #45474F;
        animation: fadeInAnimation 0.8s ease-in-out forwards;
      }
    </style>
    <rect
        data-testid="card-bg"
        x="1%"
        y="0.5%"
        rx="25"
        height="99%"
        stroke="#319795"
        width="98%"
        fill="#ffffff"
      />

      <rect
      data-testid="card-bg"
      x="7%"
      y="20%"
      rx="5"
      height="70%"
      width="86%"
      fill="#EFF8FF"
    /> 
  
    <text x="7%" y="13%" class="title">${title}</text>

    <text x="14%" y="32%" class="content">1. 프로젝트 피드백 횟수:</text>
    <text x="14%" y="42%" class="content">2. 코드 리뷰 횟수:</text>
    <text x="14%" y="52%" class="content">3. 반영된 프로젝트 피드백 수:</text>
    <text x="14%" y="62%" class="content">4. 반영된 코드 리뷰 수:</text>
    <text x="14%" y="72%" class="content">5. 프로젝트 리팩토링 횟수:</text>
    <text x="14%" y="82%" class="content">6. 코드 리팩토링 횟수:</text>

    <text x="64%" y="32%" class="content">${feedbackCnt}</text>
    <text x="64%" y="42%" class="content">${codeReviewCnt}</text>
    <text x="64%" y="52%" class="content">${includedFeedbackCnt}</text>
    <text x="64%" y="62%" class="content">${includedCodeReviewCnt}</text>
    <text x="64%" y="72%" class="content">${projectRefactorCnt}</text>
    <text x="64%" y="82%" class="content">${codeRefactorCnt}</text>

  </svg>
    `;
};