JavaScript
SVG
reactjs
React

ReactでSVG Componentをカスタマイズ

作ったもの

とりあえず、作ったものをはじめに載せておきます。
下記リンクから見れると思います。
サンプル

問題

ReactでSVGを扱う方法は色々あると思うが、使い勝手が悪いなと思う点が多かった。
例えば、

  • jsxで使えるようにするためにSVGのプロパティ名を変更する必要がある
  • react-svg-loaderなどを使いSVGファイルをComponentとして簡単に扱えるようにしても、カスタマイズが容易ではない

など。
そして個人的に一番実現したかったのが、
デザイナーがIllustrator等でSVGのデータを作ることがあるが、そのデータ自体には修正を加えることなく、かつブラウザ上では何か動きをつけたい
というもの。

例えば、以下のようなSVGファイルをIllustratorで作ってもらい、このSVGファイルを直接編集することなく、<image>にエフェクトを加えたり、<text>の内容を編集しようとすると案外手間がかかったりする。

sample.svg
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="TopLayer" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 2551.2 5102.4" enable-background="new 0 0 2551.2 5102.4" xml:space="preserve">
    <g id="Image">
        <image
            overflow="hidden"
            width="2710"
            height="3189"
            xlink:href="/img/sample.jpg"
            transform="matrix(0.9414 0 0 0.9067 0 1324.6071)">
        </image>
    </g>
    <g id="Background_1_">
        <rect x="0" y="4214.9" fill="#B1B1B1" width="2551.2" height="887.5"/>
    </g>
    <g id="Background">
        <rect x="0" fill="#B1B1B1" width="2551.2" height="1324.6"/>
    </g>
    <g id="Message">
        <rect x="313.2" y="421.5" fill="none" width="1937.6" height="605.9"/>
        <text transform="matrix(1 0 0 1 381.9751 597.4545)" font-family="'KozGoPr6N-Regular-90ms-RKSJ-H'" font-size="200px">あいうえおかきくけ</text>
    </g>
    <g id="Name">
        <rect x="313.2" y="4355.1" fill="none" width="1937.6" height="605.9"/>
        <text transform="matrix(1 0 0 1 481.9751 4531.1294)" font-family="'KozGoPr6N-Regular-90ms-RKSJ-H'" font-size="200px">こさしすせそたちつ</text>
    </g>
</svg>

目標

先ほどのSVGファイルの、<g id="Image">内の<image>で表示されている画像をボタンから位置調整できるようにすることを目標とする。

方法

react-samy-svg

react-samy-svgを利用する。
react-samy-svgは「Background」に記述されているが、
SVGファイルをjsxとして使うための作業(プロパティ名の変更など)や、既存のファイルを修正することなく、ReactコンポーネントとしてSVGを呼び出せ、かつカスタマイズできるというもの。
個人的にはselectorというプロパティでSVGのタグが指定できるのが強力だと感じた。

実践

react-samy-svgをインストール

$ npm install react-samy-svg

以下、<image>を位置調整するコード

import React from 'react';
import { Samy, SvgProxy } from 'react-samy-svg';

export default class SvgComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      translateX: 0,
      translateY: 0,
    };
  }

  handleClick(direction) {
    switch (direction) {
      case 'up':
        this.setState({
          translateY: this.state.translateY - 100,
        });
        break;
      case 'down':
        this.setState({
          translateY: this.state.translateY + 100,
        });
        break;
      case 'right':
        this.setState({
          translateX: this.state.translateX + 100,
        });
        break;
      case 'left':
        this.setState({
          translateX: this.state.translateX - 100,
        });
        break;
      default:
        this.setState({
          translateX: 0,
          translateY: 0,
        });
    }
  }

  render() {
    return (
      <div>
        <Samy path="/svg/sample.svg">
          <SvgProxy
            selector="#Image image"
            transform={`translate(${this.state.translateX}, ${this.state.translateY})`}
          />
        </Samy>
        <button onClick={() => this.handleClick('up')}>Up</button>
        <button onClick={() => this.handleClick('down')}>Down</button>
        <button onClick={() => this.handleClick('right')}>Right</button>
        <button onClick={() => this.handleClick('left')}>Left</button>
      </div>
    );
  }
}

以下、コードの解説

SamySvgProxyをインポートする。

Samy

<Samy>pathに読み込むSVGファイルのパスを記述。

SvgProxy

<SvgProxy><Samy>内に記述する。
上のコードのようにすることでSVGファイルの中からimageタグを指定することができる。この機能が個人的にいい。

<SvgProxy selector="#Image image" />

あとは普通にSVGをカスタマイズするように、Reactのstateなりpropsなりを渡してあげればカスタマイズできる。今回の例ではボタンからの入力に応じて<image>を位置調整したいので以下のようにした。

<SvgProxy
    selector="#Image image" 
    transform={`translate(${this.state.translateX}, ${this.state.translateY})`}
/>

ちなみに、以下のようにすると<text>の中身も変更できる。とても便利。

<SvgProxy selector="#hoge text">
    {text}
</SvgProxy>

所感

以上のように、react-samy-svgを利用すれば、手間いらずで結構自由度高くReactのコンポーネントとしてSVGをカスタマイズできると思います。
また、カスタマイズしたい箇所にはあらかじめIllustratorから特定のidを付けてもらうなどしてデザイナーと連携すれば効率が上がるのではないかなと思います。

補足

react-samy-svgを使い始めて数日後、同じ作者によって新しくreact-svgmtというツールが作成されていました。
今のところ機能はほぼ同じようですが、これからは後者を更新していくのかなって感じなので、react-svgmtを使うのがいいかもです。

追記

また、やはりreact-svg-samyには不具合があるようなので、react-svgmtを使用することを強く勧めます。
不具合の例としては、<Samy path={path}>pathが変更され再レンダリングされた際に<SvgProxy>内が正しく更新されないなど。