Home About Contact
React , SVG

React Component で SVG アイコンをダイナミックに生成する

icons

Webアプリでアイコンをたくさんつくる必要が生じた。 はじめは生成AIに頼んでつくっていたが、バリエーションが増えすぎたので ラベルと幅と色を指定して SVG アイコンを生成する React Component を書いた。

文字列とサイズと色を指定してアイコンを生成

icons

コードはこれ:

import React from 'react'

// @ts-ignore
import MyIcon, { MyBlueIcon, MyRedIcon, MyOrangeIcon, MyGreenIcon } from './MyIcon'

const App = (): React.ReactElement => {
    return (
        <>
            <MyIcon
                label="Hello, World!"
                width={120}
                fillColor="#1E90FF"
                strokeColor="#0080FF"
            />

            <br/>

            <MyIcon
                label="Hello"
                width={84}
                fillColor="#1E90FF"
                strokeColor="#0080FF"
            />

            <br/>

            <MyIcon
                label="OK"
                width={48}
                fillColor="#22C55E"
                strokeColor="#16A34A"
            />
        </>
    )
}

export default App

ラベル文字列の長さに応じて適当なアイコンの幅を指定しています。

色を個別に指定していくが面倒なので、 MyBlueIcon, MyRedIcon, ... のように色だけあらかじめ指定してアイコンを用意しました。

icons

コードはこれ:

import React from 'react'

// @ts-ignore
import MyIcon, { MyBlueIcon, MyRedIcon, MyOrangeIcon, MyGreenIcon } from './MyIcon'

const App = (): React.ReactElement => {
    return (
        <>
            <MyBlueIcon label="Hello, World!" width={120} />

            <br/>
            <MyRedIcon label="Hello, World!" width={120} />

            <br/>
            <MyOrangeIcon label="Hello, World!" width={120} />

            <br/>
            <MyGreenIcon label="Hello, World!" width={120} />
        </>
    )
}

export default App

与えた文字列の長さに応じてその幅を自動計算できるとうれしいのですが。 React って JS を実行できたりするのかな。 ただ、そこまでやりはじめると、フォント名やフォントサイズの指定も必要。 アイコンの縦の大きさも計算したりとややこしい話になる。 特定の状況下で簡単に使いたいだけなので、このくらいでちょうどよい気がする。

最後に SVG を生成している本体になる MyIcon.tsx はこちら:

import React from 'react'

const style0: React.CSSProperties = {
    stopColor: 'white',
    stopOpacity: 0
}

const style1: React.CSSProperties = {
    stopColor: 'white',
    stopOpacity: 1
}

export const MyBlueIcon = (props: { label: string; width: number }): React.ReactElement => {
    return (
        <MyIcon
            label={props.label}
            width={props.width}
            fillColor="#1E90FF"
            strokeColor="#0080FF"
        />
    )
}

export const MyRedIcon = (props: { label: string; width: number }): React.ReactElement => {
    return (
        <MyIcon
            label={props.label}
            width={props.width}
            fillColor="#EF4444"
            strokeColor="#DC2626"
        />
    )
}

export const MyOrangeIcon = (props: { label: string; width: number }): React.ReactElement => {
    return (
        <MyIcon
            label={props.label}
            width={props.width}
            fillColor="#FF8C00"
            strokeColor="#E67E00"
        />
    )
}

export const MyGreenIcon = (props: { label: string; width: number }): React.ReactElement => {
    return (
        <MyIcon
            label={props.label}
            width={props.width}
            fillColor="#22C55E"
            strokeColor="#16A34A"
        />
    )
}

const MyIcon = (props: {
    label: string
    width: number
    fillColor: string
    strokeColor: string
}): React.ReactElement => {

    return (
        <svg xmlns="http://www.w3.org/2000/svg" viewBox={`0 0 ${props.width} 24`} width={`${props.width}`} height="24">
          <rect x="1" y="1" width={`${props.width-2}`} height="22" rx="3" ry="3" 
                fill={props.fillColor} stroke={props.strokeColor} stroke-width="0.5"/>

          <rect x="1" y="1" width={`${props.width-2}`} height="11" rx="3" ry="3" 
                fill="url(#myGradient)" opacity="0.3"/>

          <text x={`${props.width/2}`} y="17" fontFamily="Arial, sans-serif" fontSize="14" 
                fontWeight="bold" textAnchor="middle" fill="white">{props.label}</text>
        
          <defs>
            <linearGradient id="myGradient" x1="0%" y1="0%" x2="0%" y2="100%">
              <stop offset="0%" style={style1} />
              <stop offset="100%" style={style0} />
            </linearGradient>
          </defs>
        </svg>
    )
}

export default MyIcon

MyIcon の色を指定済みのものとして MyBlueIcon, MyRedIcon, ... を定義しているだけ。 とてもわかりやすいけれど、それだけのことしかしていないのにこれだけのコードを書く必要があるというは冗長ではある。

AIに聞いてみたら解決方法あるんだろうね。たぶん。