SVG内の色をプログラムから指定する

こんにちは。 ピリカ開発チームの伊藤です。

SVGは解像度や拡大縮小に依らずきれいな描画が得られるため、アイコンなどの描画に最適なフォーマットです。 SVGフォーマットの画像にある色などは外部からスタイルを上書きすることで色を変更できます。

例えば、地図上のマーカーを状態により色を変更するといった用途で使用することができます。

f:id:pirika-inc:20210909093714p:plain

ここではReactにSVGファイルを読み込ませて、色を動的に変更する方法をご紹介します

前提条件

  • React 16.14
  • Material UI 4.11

今回はこのような画像を使用します。

f:id:iseebi:20211004084949p:plain

扱いやすくフォーマットする

SVGXMLがベースとなっているフォーマットですが、Illustrator等から書き出された状態では、インデントや改行がすべて除去された状態になっており人間が作業するには向かない状態となっています。xmllintを使用してフォーマットすることで読みやすくなります。

$ xmllint --format --output out.svg input.svg

クラスを特定する

例えば、Illustratorから出力されたSVGはこのような形で出力されています。

styleタグ内にCSSクラスが書かれており、塗りの色が定義されていて、実際の描画を担うpathやpolygonからそのクラス名が参照されていることがわかります。

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160">
  <defs>
    <style>.cls-1{fill:#1470cc;}.cls-2{fill:#fff;}</style>
  </defs>
  <g id="&#x30EC;&#x30A4;&#x30E4;&#x30FC;_2" data-name="&#x30EC;&#x30A4;&#x30E4;&#x30FC; 2">
    <g id="&#x30A8;&#x30EC;&#x30E1;&#x30F3;&#x30C8;">
      <path class="cls-1" d="M80,0a80,80,0,1,0,80,80A80,80,0,0,0,80,0Z"/>
      <polygon class="cls-2" points="116.37 128 77.81 128 73.32 57 120.85 57 116.37 128"/>
      <polygon class="cls-2" points="106.99 39.25 108.83 34.84 100.82 31.51 98.98 35.92 78.97 27.61 76.22 34.23 124.25 54.2 127 47.57 106.99 39.25"/>
      <path class="cls-2" d="M75,74c0-.08,0-.17,0-.25a14.21,14.21,0,0,0,2.27-1.24,15,15,0,1,0-4.52-27A14.94,14.94,0,0,0,54,60c0,.08,0,.17,0,.25A15,15,0,0,0,46.2,79.87a15,15,0,0,0-2.81,21.62A15,15,0,0,0,52,128v0h52V74Z"/>
      <polygon class="cls-1" points="107 57 69.01 57 68.6 52 107 52 107 57"/>
      <polygon class="cls-1" points="73.36 132.4 68.64 52.74 73.31 52.74 77.86 132.18 73.36 132.4"/>
    </g>
  </g>
</svg>

プログラムから参照するときにわかりやすいよう、名前を変えましょう。今回は背景色の青い部分を変えたかったので、cls-1をfillColorに変更しました。

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160">
  <defs>
    <style>.fillColor{fill:#1470cc;}.cls-2{fill:#fff;}</style>
  </defs>
  <g id="&#x30EC;&#x30A4;&#x30E4;&#x30FC;_2" data-name="&#x30EC;&#x30A4;&#x30E4;&#x30FC; 2">
    <g id="&#x30A8;&#x30EC;&#x30E1;&#x30F3;&#x30C8;">
      <path class="fillColor" d="M80,0a80,80,0,1,0,80,80A80,80,0,0,0,80,0Z"/>
      <polygon class="cls-2" points="116.37 128 77.81 128 73.32 57 120.85 57 116.37 128"/>
      <polygon class="cls-2" points="106.99 39.25 108.83 34.84 100.82 31.51 98.98 35.92 78.97 27.61 76.22 34.23 124.25 54.2 127 47.57 106.99 39.25"/>
      <path class="cls-2" d="M75,74c0-.08,0-.17,0-.25a14.21,14.21,0,0,0,2.27-1.24,15,15,0,1,0-4.52-27A14.94,14.94,0,0,0,54,60c0,.08,0,.17,0,.25A15,15,0,0,0,46.2,79.87a15,15,0,0,0-2.81,21.62A15,15,0,0,0,52,128v0h52V74Z"/>
      <polygon class="fillColor" points="107 57 69.01 57 68.6 52 107 52 107 57"/>
      <polygon class="fillColor" points="73.36 132.4 68.64 52.74 73.31 52.74 77.86 132.18 73.36 132.4"/>
    </g>
  </g>
</svg>

SVGを設置する

この画像をReactプログラムから参照して設置します。

ReactのプログラムからはSVGをReactComponentとして直接importすることができます。

import * as React from "react";
import { WithStyles, withStyles } from "@material-ui/core";
import styles from "./PostMapPinStyles";
import { ReactComponent as PostImage } from "../../resources/post.svg";
import classNames from "classnames";

interface Props extends WithStyles<typeof styles> {
  isActive: boolean;
  onClick: () => void;
}

const PostMapPin: React.FC<Props> = ({ isActive, onClick, classes }) => (
  <div className={classNames(classes.root, isActive ? classes.active : classes.normal)} onClick={onClick}>
    <PostImage />
  </div>
);

export default withStyles(styles)(PostMapPin);

CSSを上書きする

当該のReactComponent下には、 SVGタグが生成されていますので、そのSVGタグ以下のクラス名を指定して上書きすることで色を変更できます。Material UIのスタイルで以下のように記述しました。

  return createStyles({
    root: {
      // ...
    },
    normal: {
      "& svg .fillColor": {
        fill: "#1470cc"
      }
    },
    active: {
      "& svg .fillColor": {
        fill: "#df7627"
      }
    }
  });

あとは、isActiveのtrue/falseを切り替えることで、描画される色が変更されます。