ReactでSVG要素をドラッグして移動、拡大縮小 

# はじめに
ReactでSVG要素を書いてそれをドラッグしたくなったのですが、自分で`onMouseDown()`とか書くのは嫌だなぁと思いました。
何度かやったことがありますが細かいところでめんどくさいです。マウスダウンしたまま、ウィンドウの外に出した場合とか、移動しすぎ、拡大縮小しすぎた場合とか、やってみるとたくさんあるんです。

こんなの誰かがモジュール作ってくれてるでしょうと思ったらやはりありました。今回、この`react-zoom-pan-pinch`の使い方をさらっと説明します。インストールとか割愛。

https://www.npmjs.com/package/react-zoom-pan-pinch

今回の私のコードは下記。(canvasって名づけてしまったけどsvgです）

https://github.com/yo16/react_canvas_prac


# 使い方
```JavaScript:MySvg1.js
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch';

import "./MySvg1.css";

const MySvg1 = () => {
    return (
        <div>
          sample1. TransformComponentの中にsvgタグ
          <TransformWrapper>
            <TransformComponent>
              <svg width={400} height={400} >
                <rect x="50" y="50" width="100" height="100" fill="blue" />
                <circle cx="250" cy="150" r="50" fill="red" />
                <text x="50" y="250" fontSize="20" fill="green">Hello, SVG!</text>
              </svg>
            </TransformComponent>
          </TransformWrapper>
        </div>
    );
};
```
`<TransformWrapper>`タグと、`<TransformComponent>`タグで括った中の要素が、全体的にドラッグできるようになります。それだけ。

画像を貼ってもあまりありがたみはありませんが、こんな感じです。境界を見たかったので、背景色をちょっと変えてます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/37480339-38ed-f51b-d5c1-998bf9f2ea81.png)

# ちょっとした注意点１
上の書き方をすると、`<svg width={400} height={400} >`がドラッグできる状態になるので、ドラッグすると`<svg>`の角が見えるんですね。`<svg>`に背景色をつけていると、その角が見える状態です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/6270f91a-3be4-657c-a312-1b3550acdc7d.png)
（↑右下にドラッグした状態）

公式のサンプル（[Examples / SVG Zoom To Element](https://bettertyped.github.io/react-zoom-pan-pinch/?path=/story/examples-svg-zoom-to-element--svg-zoom-to-element)）ではそんな状態ではなかったので、よーく見ると、`<TransformWrapper>`から生成されていると思われる`<div>`に`class="react-transform-wrapper"`があって、`.react-transform-wrapper`で色を付けている。

ということで回避策は、自分のcssに、`.react-transform-wrapper`の背景色を設定することでした。

```css:MySvg1.css
.react-transform-wrapper {
    background-color: #edd;
}
```
これでピンクっぽい色がついてます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/1d7000b5-02c3-0cfc-1e21-84fe12662c73.png)
（↑わかりづらいけど回避策実施後。右端もちゃんと切れてOK。）

# ちょっとした注意点２
SVGのwidth(400)とheight(400)より、外の要素を作ってみました。`<rect x="350" ...` と`<circle x="460" ...`です。

```JSX:MySvg1.js（抜粋）
<TransformWrapper>
  <TransformComponent>
    <svg width={400} height={400} >
      <rect x="50" y="50" width="100" height="100" fill="blue" />
      <circle cx="250" cy="150" r="50" fill="red" />
      <text x="50" y="250" fontSize="20" fill="green">Hello, SVG!</text>
      <rect x="350" y="50" width="200" height="100" fill="lightblue" />
      <circle cx="460" cy="150" r="50" fill="green" />
    </svg>
  </TransformComponent>
</TransformWrapper>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/2d461565-e72a-4c53-e6e4-184ccd0b79ef.png)

ドラッグしてみると・・・↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/e69226ee-3cfc-0bd8-b7d9-5f0c62473d1f.png)

ないことになってる。（ように見える）

そうか。ドラッグによって`<svg>`ごと移動されてるから、表示されてないんですね。`<svg>`の右下が見えているということ。上の注意点１をやったがために、消えたと勘違いしました。

対策は"ちょっとした"注意点ではなくなる予感がしますね。ここは"ちょっと"で抑えるために、力業発動。スマートな別解があるかもしれません。

## 対策1: `<svg>`を広げる
まず、`<svg>`要素を、400x400だったのを、550x400に広げました。このサイズは、動的に自分で計算するか、広めにとっておくかどちらか。ここが力業たるゆえんです。
```JSX:MySvg1.js（抜粋）
<svg width={550} height={400} style={{backgroundColor: "#dde"}} >
  <rect x="50" y="50" width="100" height="100" fill="blue" />
  <circle cx="250" cy="150" r="50" fill="red" />
  <text x="50" y="250" fontSize="20" fill="green">Hello, SVG!</text>
  <rect x="350" y="50" width="200" height="100" fill="lightblue" />
  <circle cx="460" cy="150" r="50" fill="green" />
</svg>
```
※ ついでに、`<svg>`の範囲がわかるように、`style`で水色#ddeを付けてます。本番では不要。

横幅を400から550にしたので、全部表示されます。でも横長になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/65e67dac-ff00-da32-f7db-b8c03b902620.png)


でも400x400にしたい！ということで・・・

## 対策2: `<TransformWrapper>`の幅を400にする
背景色を設定するために使ったcssをもう一度使います。

```css:MySvg1.css
.react-transform-wrapper {
    background-color: #edd;
    width: 400px;
    height: 400px;
}
```

こうすることで、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/b2edb5a5-be59-052c-f712-eae5480a56ee.png)

からの～ドラッグで

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722610/b4ac987b-4ecb-6464-0e4e-b2c28210dc38.png)

はい！きた！！
※ svgとその外の境界がわかるように、svgにつけた背景色のおかげで水色の角がありますが、設定を消せば見えません！

でっかい`<svg>`の一部を表示してドラッグする機能というのは、ちょっと引っかかるけど、まぁ解の１つではあるからいいか。

# ちなみに
ChatGPTさんに聞いたら、`react-canvas`というモジュールもあるよって言われたから、調べたりあれこれ聞いてるうちに、古いから使わない方がいいよと言われました。2024年時点でLast publishが7年前。うん、まぁ確かに。ありがとう。でも、うん・・・はい。

# おわりに
`react-zoom-pan-pinch`は、この手のシステムでは全員必要なんじゃないでしょうか。紹介した以外にも、特定の要素をズームしたり、初期に戻したり、痒い所に手が届くモジュールです。
私も今後とも使い続けます。

`svg-zoom-pan`というJavaScriptのライブラリがあって、ndmもあるんですが、そちらは調べきれてないです。svg限定だとしたら、そちらの方がよいのかもしれない。
でもJavaScriptの方はメンテされているけどndmは古かったり、`react-zoom-pan-pinch`はドラッグした後のanimationがデフォルトであるけど`svg-zoom-pan`はなさそうとか、そのあたりが気になりました。ちょっとやりかけてやめた。そのうち調べるかも。

https://github.com/bumbu/svg-pan-zoom?tab=readme-ov-file

いいやり方などありましたら、コメント欄で是非教えてください！:grinning: