Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Tags
more
Archives
Today
Total
관리 메뉴

juni

SVG 관련 퍼블리싱 ( 뱃지 ) 본문

인턴

SVG 관련 퍼블리싱 ( 뱃지 )

juni_shin 2024. 11. 15. 10:35

뱃지 공통 로직 분리 예시

import { useRef, useState, useEffect } from "react";
import { toSvg } from "html-to-image";
import { useCertificateContext } from "@/lib/provider/CertificateProvider";
import { use} from "@/lib/provider/Provider";
import { DEFAULT_IMAGE_URL } from "@/lib/constants";

export const useBadgeLogic = ({
  formData,
  onFileReady,
}: {
  formData?: any;
  onFileReady?: (file: Blob) => void;
}) => {
  const refContainer = useRef<any>(null);
  const badgeTypeRef = useRef<SVGSVGElement>(null);
  const { selectedBadge } = useCertificateContext();
  const { Info } = use();
  const [width, setWidth] = useState(0);
  const [symbolImage, setSymbolImage] = useState<string | null>(null);

  const convertSvgToFile = async () => {
    if (!refContainer.current) throw new Error("SVG Element not found");

    try {
      // html-to-image 라이브러리를 사용해 refContainer를 PNG로 변환
      const dataUrl = await toSvg(refContainer.current);

      // PNG를 Blob으로 변환
      const response = await fetch(dataUrl);
      const blob = await response.blob();

      // 상위 컴포넌트로 Blob 파일 전달
      if (onFileReady) {
        onFileReady(blob); // PNG 형식의 Blob 파일을 상위 컴포넌트로 전달
      }

      return blob; // Blob 파일 반환
    } catch (error) {
      throw new Error("Failed to convert SVG to PNG");
    }
  };

  const showingSymbolImage = symbolImage
    ? symbolImage
    : Info.images.club_symbol
    ? DEFAULT_IMAGE_URL + Info.images.club_symbol[0].path
    : "";

  useEffect(() => {
    const element = refContainer.current; // ref 값을 변수에 저장

    if (!element) return;

    const resizeObserver = new ResizeObserver((entries) => {
      setWidth(entries[0].contentRect.width); // 너비 정보를 상태로 저장
    });
    resizeObserver.observe(element); // ref 요소 감시 시작

    return () => resizeObserver.disconnect(); // 컴포넌트 언마운트 시 감시 중단
  }, []);

  useEffect(() => {
    if (formData?.achievement_form_symbol) {
      setSymbolImage(URL.createObjectURL(formData.achievement_form_symbol[0])); // File 객체를 URL로 변환
    }
  }, [formData?.achievement_form_symbol]);

  return {
    refContainer,
    badgeTypeRef,
    selectedBadge,
    width,
    showingSymbolImage,
    convertSvgToFile,
  };
};


뱃지 예시 ( svg파일 이용하여 구현 예시 )

"use client";

import { forwardRef, useImperativeHandle } from "react";
import { removeControlCharacters, wrapText } from "@/lib/utils";
import { useBadgeLogic } from "./useBadgeLogic";

const BadgeType45 = forwardRef(
  (
    {
      formData,
      onFileReady,
    }: { formData?: any; onFileReady?: (file: Blob) => void },
    ref
  ) => {
    const {
      refContainer,
      badgeTypeRef,
      selectedBadge,
      width,
      showingSymbolImage,
      convertSvgToFile,
    } = useBadgeLogic({
      formData,
      onFileReady,
    });

    // Replace this part
    useImperativeHandle(ref, () => ({ convertSvgToFile }));

    // 줄바꿈
    const description = removeControlCharacters(
      formData?.name || "이 곳은 인증서의 제목이 들어갑니다."
    );
    const lines = wrapText(description, 280); // 너비 300 기준으로 줄바꿈
    const lineHeight = 1.2; // 각 줄의 높이 비율
    const fontSize = 36; // 텍스트의 폰트 크기
    const totalHeight = lines.length * fontSize * lineHeight; // 텍스트의 총 높이 계산
    const svgHeight = 650;
    const startY = (svgHeight - totalHeight) / 1.85;

    return (
      <div
        ref={refContainer}
        className="w-full relative overflow-hidden aspect-square"
      >
        <div
          className="w-[600px] h-[600px] relative origin-top-left"
          style={{
            transform: `scale(${width / 600})`,
          }}
        >
          <svg
            ref={badgeTypeRef}
            width="600"
            height="600"
            viewBox="0 0 600 600"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M500 232C500 229.791 501.791 228 504 228H567.271C570.052 228 571.984 230.767 571.026 233.378L553.506 281.122C553.179 282.012 553.179 282.988 553.506 283.878L571.026 331.622C571.984 334.233 570.052 337 567.271 337H504C501.791 337 500 335.209 500 333V232Z"
              fill={selectedBadge.mainColor}
            />
            <rect
              x="512"
              y="228"
              width="43"
              height="109"
              fill="url(#paint0_linear_913_2343)"
            />
            <path
              d="M100 232C100 229.791 98.2091 228 96 228H32.7287C29.9479 228 28.0156 230.767 28.9736 233.378L46.4943 281.122C46.8208 282.012 46.8208 282.988 46.4943 283.878L28.9736 331.622C28.0156 334.233 29.9479 337 32.7287 337H96C98.2091 337 100 335.209 100 333V232Z"
              fill={selectedBadge.mainColor}
            />
            <rect
              width="46"
              height="109"
              transform="matrix(-1 0 0 1 89 228)"
              fill="url(#paint1_linear_913_2343)"
            />
            <g filter="url(#filter0_d_913_2343)">
              <path
                d="M499.127 413.599L314.696 529.73C305.655 535.423 294.345 535.423 285.304 529.73L100.873 413.599C92.2664 408.183 87 398.412 87 387.874V96.9993C87 80.4255 99.7853 67 115.569 67H484.431C500.215 67 513 80.4255 513 96.9993V387.857C513 398.395 507.734 408.167 499.127 413.583V413.599Z"
                fill={selectedBadge.mainColor}
              />
            </g>
            <path
              d="M487.909 407.787L313.868 517.95C305.336 523.35 294.664 523.35 286.132 517.95L112.091 407.787C103.97 402.649 99 393.38 99 383.383V107.458C99 91.7355 111.065 79 125.959 79H474.041C488.935 79 501 91.7355 501 107.458V383.368C501 393.364 496.03 402.634 487.909 407.771V407.787Z"
              fill="#101113"
            />
            <path
              d="M480.43 404.138L313.316 511.068C305.124 516.311 294.876 516.311 286.684 511.068L119.57 404.138C111.772 399.151 107 390.154 107 380.451V112.622C107 97.3618 118.585 85 132.886 85H467.114C481.415 85 493 97.3618 493 112.622V380.436C493 390.139 488.228 399.136 480.43 404.123V404.138Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M112 112.164C112 98.909 122.745 88.1639 136 88.1639H464C477.255 88.1639 488 98.909 488 112.164V291.164H112V112.164Z"
              fill="#101113"
            />
            <path
              d="M367.894 150.018C369.282 149.126 371.97 132.854 394.202 135.074C393.314 141.235 385.708 147.716 379.867 149.506L379.873 150.091L389.343 151.149C393.873 143.489 403.172 139.713 411.831 140.877C419.554 141.912 418.981 143.104 417.095 145.455C414.037 149.265 406.603 153.469 401.541 153.529L401.547 154.114L411.037 157.105C418.476 148.66 432.441 148.989 440.95 155.445C427.578 168.697 413.129 156.678 429.679 167.538C440.168 161.629 451.785 163.649 459.856 172.636C454.991 176.879 445.692 177.485 439.797 174.765L439.411 175.159L446.626 182.23C453.599 179.827 462.192 180.975 467.898 185.874C469.283 187.055 475.391 193.908 473.788 195.282C471.628 197.124 459.186 195.471 454.253 191.425C459.299 200.971 464.037 200.98 467.921 205.768C471.402 210.064 473.299 220.032 470.626 224.862C461.251 220.358 456.428 212.8 457.07 202.408C437.831 171.584 407.072 153.204 371.599 154.003C369.414 154.049 367.688 152.182 367.91 150.018L367.894 150.018Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M408.957 162.342C420.85 165.082 425.815 176.271 421.571 187.318C412.172 182.838 406.713 172.613 408.957 162.342Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M448.141 217.546C440.63 210.202 438.94 198.216 445.543 189.771C454.577 196.725 455.938 209.113 448.141 217.546Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M436.183 200.493C427.457 194.324 424.469 183.318 428.571 173.533C430.164 172.761 445.974 184.327 436.183 200.493Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M387.261 155.994C405.849 157.041 406.143 176.477 404.471 177.444C394.973 175.265 386.256 165.919 387.261 155.994Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M231.804 150.018C230.416 149.126 227.728 132.854 205.496 135.074C206.384 141.235 213.99 147.716 219.831 149.506L219.825 150.091L210.355 151.149C205.825 143.489 196.526 139.713 187.867 140.877C180.144 141.912 180.717 143.104 182.603 145.455C185.661 149.265 193.095 153.469 198.157 153.529L198.151 154.114L188.661 157.105C181.222 148.66 167.257 148.989 158.748 155.445C172.12 168.697 186.569 156.678 170.019 167.538C159.53 161.629 147.913 163.649 139.842 172.636C144.707 176.879 154.006 177.485 159.901 174.765L160.287 175.159L153.072 182.23C146.099 179.827 137.506 180.975 131.8 185.874C130.415 187.055 124.307 193.908 125.91 195.282C128.07 197.124 140.512 195.471 145.445 191.425C140.399 200.971 135.661 200.98 131.777 205.768C128.296 210.064 126.399 220.032 129.072 224.862C138.447 220.358 143.27 212.8 142.628 202.408C161.867 171.584 192.626 153.204 228.099 154.003C230.284 154.049 232.01 152.182 231.788 150.018L231.804 150.018Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M190.741 162.342C178.848 165.082 173.883 176.271 178.127 187.318C187.526 182.838 192.985 172.613 190.741 162.342Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M151.557 217.546C159.068 210.202 160.758 198.216 154.155 189.771C145.121 196.725 143.76 209.113 151.557 217.546Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M163.515 200.493C172.241 194.324 175.229 183.318 171.127 173.533C169.534 172.761 153.724 184.327 163.515 200.493Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M212.437 155.994C193.849 157.041 193.555 176.477 195.227 177.444C204.725 175.265 213.442 165.919 212.437 155.994Z"
              fill={selectedBadge.mainColor}
            />
            <circle
              cx="238.271"
              cy="153.18"
              r="2.42188"
              fill={selectedBadge.mainColor}
            />
            <circle
              cx="361.578"
              cy="153.18"
              r="2.42188"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M108 374.275C108 382.207 111.919 389.627 118.47 394.098L288.725 510.304C295.525 514.946 304.475 514.946 311.275 510.304L481.53 394.098C488.081 389.627 492 382.207 492 374.275V283H108L108 374.275Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M108 374.275C108 382.207 111.919 389.627 118.47 394.098L288.725 510.304C295.525 514.946 304.475 514.946 311.275 510.304L481.53 394.098C488.081 389.627 492 382.207 492 374.275V283H108L108 374.275Z"
              fill={selectedBadge.subColor}
            />
            <rect
              x="220"
              y="408"
              width="160"
              height="48"
              fill="url(#pattern0_913_2343)"
            />
            <mask id="path-24-inside-1_913_2343" fill="white">
              <path d="M92 365.706C92 373.951 96.2314 381.618 103.207 386.012L289.339 503.283C295.854 507.388 304.146 507.388 310.661 503.283L496.793 386.012C503.769 381.618 508 373.951 508 365.706V275H92L92 365.706Z" />
            </mask>
            <path
              d="M92 365.706C92 373.951 96.2314 381.618 103.207 386.012L289.339 503.283C295.854 507.388 304.146 507.388 310.661 503.283L496.793 386.012C503.769 381.618 508 373.951 508 365.706V275H92L92 365.706Z"
              stroke="#101113"
              stroke-width="8"
              mask="url(#path-24-inside-1_913_2343)"
            />
            <rect
              x="60"
              y="252"
              width="480"
              height="148.797"
              rx="4"
              fill="url(#paint2_linear_913_2343)"
            />
            <rect
              x="60"
              y="262"
              width="480"
              height="129"
              fill="white"
              fill-opacity="0.3"
            />
            <path
              d="M513 252V228L536 252H513Z"
              fill={selectedBadge.mainColor}
            />
            <path
              d="M513 252V228L536 252H513Z"
              fill="black"
              fill-opacity="0.4"
            />
            <path d="M87 252V228L62 252H87Z" fill={selectedBadge.mainColor} />
            <path d="M87 252V228L62 252H87Z" fill="black" fill-opacity="0.4" />
            <path d="M60 262L540 262" stroke="white" stroke-width="3" />
            <path d="M60 390.797L540 390.797" stroke="white" stroke-width="3" />
            <path
              d="M249.277 210.328C248.984 208.629 247.617 207.652 245.898 207.652C243.574 207.652 241.953 209.43 241.953 212.633C241.953 215.875 243.594 217.613 245.898 217.613C247.578 217.613 248.945 216.676 249.277 215.016H251.836C251.445 217.73 249.219 219.898 245.859 219.898C242.129 219.898 239.395 217.184 239.395 212.633C239.395 208.062 242.168 205.367 245.859 205.367C248.984 205.367 251.406 207.184 251.836 210.328H249.277ZM254.023 219.703V205.562H263.223V207.691H256.562V211.559H262.734V213.688H256.562V217.555H263.262V219.703H254.023ZM265.664 219.703V205.562H270.977C274.219 205.562 275.957 207.379 275.957 210.074C275.957 211.998 275.078 213.424 273.418 214.098L276.465 219.703H273.633L270.859 214.527H268.203V219.703H265.664ZM268.203 212.398H270.586C272.5 212.398 273.359 211.578 273.359 210.074C273.359 208.57 272.5 207.691 270.586 207.691H268.203V212.398ZM277.52 207.691V205.562H288.789V207.691H284.434V219.703H281.895V207.691H277.52ZM293.32 205.562V219.703H290.781V205.562H293.32ZM295.957 219.703V205.562H305.02V207.691H298.496V211.559H304.395V213.688H298.496V219.703H295.957ZM309.707 205.562V219.703H307.168V205.562H309.707ZM321.875 210.328C321.582 208.629 320.215 207.652 318.496 207.652C316.172 207.652 314.551 209.43 314.551 212.633C314.551 215.875 316.191 217.613 318.496 217.613C320.176 217.613 321.543 216.676 321.875 215.016H324.434C324.043 217.73 321.816 219.898 318.457 219.898C314.727 219.898 311.992 217.184 311.992 212.633C311.992 208.062 314.766 205.367 318.457 205.367C321.582 205.367 324.004 207.184 324.434 210.328H321.875ZM328.242 219.703H325.527L330.508 205.562H333.633L338.633 219.703H335.918L334.746 216.207H329.414L328.242 219.703ZM330.098 214.156H334.062L332.129 208.453H332.012L330.098 214.156ZM337.969 207.691V205.562H349.238V207.691H344.883V219.703H342.344V207.691H337.969ZM351.23 219.703V205.562H360.43V207.691H353.77V211.559H359.941V213.688H353.77V217.555H360.469V219.703H351.23Z"
              fill="white"
            />
            <rect
              x="513"
              y="252"
              width="426"
              height="24"
              transform="rotate(180 513 252)"
              fill="url(#paint3_linear_913_2343)"
              fill-opacity="0.3"
            />
            <path
              d="M89.8047 400.797H510.308C504.931 412.02 495.604 415.88 481.267 424.797H118.619C100.109 413.053 94.1222 410.906 89.8047 400.797Z"
              fill="url(#paint4_linear_913_2343)"
              fill-opacity="0.3"
            />
            <rect
              x="254.487"
              y="117.072"
              width="91.0267"
              height="58.924"
              fill={selectedBadge.subColor}
              stroke={selectedBadge.mainColor}
              stroke-width="7"
              stroke-linejoin="round"
            />
            <mask id="path-36-inside-2_913_2343" fill="white">
              <path d="M270.648 142.544C270.648 141.439 271.544 140.544 272.648 140.544H286.228C287.332 140.544 288.228 141.439 288.228 142.544V163.804C288.228 165.343 286.562 166.305 285.229 165.537L280.437 162.774C279.819 162.417 279.057 162.417 278.439 162.774L273.647 165.537C272.314 166.305 270.648 165.343 270.648 163.804V142.544Z" />
            </mask>
            <path
              d="M270.648 142.544C270.648 141.439 271.544 140.544 272.648 140.544H286.228C287.332 140.544 288.228 141.439 288.228 142.544V163.804C288.228 165.343 286.562 166.305 285.229 165.537L280.437 162.774C279.819 162.417 279.057 162.417 278.439 162.774L273.647 165.537C272.314 166.305 270.648 165.343 270.648 163.804V142.544Z"
              fill={selectedBadge.subColor}
              stroke={selectedBadge.mainColor}
              stroke-width="8"
              mask="url(#path-36-inside-2_913_2343)"
            />
            <circle
              cx="279.439"
              cy="139.624"
              r="10.8217"
              fill={selectedBadge.subColor}
              stroke={selectedBadge.mainColor}
              stroke-width="6"
            />
            <path
              d="M304.018 137.291L324.477 137.287"
              stroke={selectedBadge.mainColor}
              stroke-width="6"
              stroke-linecap="round"
            />
            <path
              d="M303.741 150.361H331.737"
              stroke={selectedBadge.mainColor}
              stroke-width="6"
              stroke-linecap="round"
            />
            <defs>
              <filter
                id="filter0_d_913_2343"
                x="75"
                y="55"
                width="450"
                height="491"
                filterUnits="userSpaceOnUse"
                color-interpolation-filters="sRGB"
              >
                <feFlood flood-opacity="0" result="BackgroundImageFix" />
                <feColorMatrix
                  in="SourceAlpha"
                  type="matrix"
                  values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
                  result="hardAlpha"
                />
                <feOffset />
                <feGaussianBlur stdDeviation="6" />
                <feComposite in2="hardAlpha" operator="out" />
                <feColorMatrix
                  type="matrix"
                  values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"
                />
                <feBlend
                  mode="normal"
                  in2="BackgroundImageFix"
                  result="effect1_dropShadow_913_2343"
                />
                <feBlend
                  mode="normal"
                  in="SourceGraphic"
                  in2="effect1_dropShadow_913_2343"
                  result="shape"
                />
              </filter>
              <pattern
                id="pattern0_913_2343"
                patternContentUnits="objectBoundingBox"
                width="1"
                height="1"
              >
                <use transform="matrix(0.00125 0 0 0.00416667 0 -1.16667)" />
              </pattern>
              <linearGradient
                id="paint0_linear_913_2343"
                x1="471.905"
                y1="276"
                x2="544.303"
                y2="260.642"
                gradientUnits="userSpaceOnUse"
              >
                <stop />
                <stop offset="1" stop-opacity="0" />
              </linearGradient>
              <linearGradient
                id="paint1_linear_913_2343"
                x1="-42.8919"
                y1="48"
                x2="34.0781"
                y2="30.5323"
                gradientUnits="userSpaceOnUse"
              >
                <stop />
                <stop offset="1" stop-opacity="0" />
              </linearGradient>
              <linearGradient
                id="paint2_linear_913_2343"
                x1="60"
                y1="326.398"
                x2="540"
                y2="326.398"
                gradientUnits="userSpaceOnUse"
              >
                <stop stop-color={selectedBadge.mainColor} />
                <stop offset="0.215" stop-color={selectedBadge.subColor} />
                <stop offset="0.75" stop-color={selectedBadge.subColor} />
                <stop offset="1" stop-color={selectedBadge.mainColor} />
              </linearGradient>
              <linearGradient
                id="paint3_linear_913_2343"
                x1="731.849"
                y1="258"
                x2="731.849"
                y2="276"
                gradientUnits="userSpaceOnUse"
              >
                <stop />
                <stop offset="1" stop-opacity="0" />
              </linearGradient>
              <linearGradient
                id="paint4_linear_913_2343"
                x1="307.213"
                y1="406.797"
                x2="307.213"
                y2="424.797"
                gradientUnits="userSpaceOnUse"
              >
                <stop />
                <stop offset="1" stop-opacity="0" />
              </linearGradient>
            </defs>

            <text
              x="50%"
              y={startY}
              textAnchor="middle"
              dominantBaseline="middle"
              fontSize={fontSize}
              fill="#101113"
              fontWeight={700}
            >
              {lines.map((line, index) => (
                <tspan
                  key={index}
                  x="50%"
                  dy={index === 0 ? 0 : `${lineHeight}em`}
                >
                  {line}
                </tspan>
              ))}
            </text>
          </svg>

          {showingSymbolImage && (
            <div
              className="absolute right-[44.7%] top-[68.6%] bg-transparent"
              style={{
                backgroundImage: `url(${showingSymbolImage})`,
                backgroundSize: "cover", // 이미지 크기 조절 (cover 또는 contain 사용 가능)
                backgroundPosition: "center", // 이미지를 가운데 정렬
                width: "65px",
                height: "65px",
              }}
            />
          )}
        </div>
      </div>
    );
  }
);

BadgeType45.displayName = "BadgeType45";

export default BadgeType45;


이후 Provider 및 뱃지 선택 모달에 제작한 뱃지 컴포넌트 추가

'인턴' 카테고리의 다른 글

SVG 관련 퍼블리싱 ( 인증서 )  (0) 2024.11.15
Next.js 404페이지 ( not-found )  (0) 2024.11.15
푸터  (3) 2024.11.15
커뮤니티 게시글 관련 QA 및 리팩토링  (1) 2024.11.14
커뮤니티 (게시글 고정) API 연동  (2) 2024.11.13