LoginSignup
2
1

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

Posted at

はじめに

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

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

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

使い方

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

ちょっとした注意点1

上の書き方をすると、<svg width={400} height={400} >がドラッグできる状態になるので、ドラッグすると<svg>の角が見えるんですね。<svg>に背景色をつけていると、その角が見える状態です。

image.png
(↑右下にドラッグした状態)

公式のサンプル(Examples / SVG Zoom To Element)ではそんな状態ではなかったので、よーく見ると、<TransformWrapper>から生成されていると思われる<div>class="react-transform-wrapper"があって、.react-transform-wrapperで色を付けている。

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

MySvg1.css
.react-transform-wrapper {
    background-color: #edd;
}

これでピンクっぽい色がついてます。

image.png
(↑わかりづらいけど回避策実施後。右端もちゃんと切れてOK。)

ちょっとした注意点2

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

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

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

image.png

ないことになってる。(ように見える)

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

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

対策1: <svg>を広げる

まず、<svg>要素を、400x400だったのを、550x400に広げました。このサイズは、動的に自分で計算するか、広めにとっておくかどちらか。ここが力業たるゆえんです。

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

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

対策2: <TransformWrapper>の幅を400にする

背景色を設定するために使ったcssをもう一度使います。

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

こうすることで、

image.png

からの~ドラッグで

image.png

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

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

ちなみに

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はなさそうとか、そのあたりが気になりました。ちょっとやりかけてやめた。そのうち調べるかも。

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1