Moveableを使って、要素を移動したりサイズ変更したりする。

# 概要

Officeのオートシェイプみたいな感じで、html要素をマウスでポチポチっと移動したり、
サイズ変更したいなぁって時ありますよね？

[Moveable](https://daybrush.com/moveable/)を使って、やってみようと思います。
今回は、React用の`react-moveable`を使います。

# 環境構築

yarnでポチポチ作っていきましょう。

```
yarn create react-app sample-react-moveable
cd sample-react-moveable
yarn add react-moveable
```

# 実装

公式サイトのサンプルを見ると、`target`を`document.querySelector()`で直接指定していますが、
これだと動かないので、`useState`と`useEffect`を使って`state`から指定してやります。

とりあえず、`src/App.js`を弄っていきます。
また、`src/App.css`に表示するオブジェクト用のクラスを追加しておきます。

```css
.moveable {
  width: 200px;
  line-height: 150px;
  text-align: center;
  margin: 50px;
  display: inline-block;
  position: absolute;
}
```

## draggable（ドラッグで移動できる）

```jsx
import React, { useState, useEffect } from 'react';
import Moveable from 'react-moveable';
import './App.css';

const App = () => {
  const [target, setTarget] = useState(null);

  useEffect(() => {
    setTarget(document.querySelector('.draggable'));
  }, []);

  return (
    <React.Fragment>
      <div className={'moveable draggable'}>draggable</div>
      <Moveable
        target={target}
        draggable={true}
        origin={false}
        throttleDrag={0}
        onDrag={e => {
          e.target.style.transform = e.transform;
        }}
      />
    </React.Fragment>
  );
};

export default App;
```
![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365084/07144b2e-da31-77d1-5a65-f4da129d202e.gif)

## resizable（ドラッグでサイズ変更できる）

```jsx
import React, { useState, useEffect } from 'react';
import Moveable from 'react-moveable';
import './App.css';

const App = () => {
  const [target, setTarget] = useState(null);

  useEffect(() => {
    setTarget(document.querySelector('.resizable'));
  }, []);

  return (
    <React.Fragment>
      <div className={'moveable resizable'}>resizable</div>
      <Moveable
        target={target}
        resizable={true}
        origin={false}
        throttleResize={0}
        keepRatio={true}
        onResize={e => {
          e.target.style.width = `${e.width}px`;
          e.target.style.height = `${e.height}px`;
        }}
      />
    </React.Fragment>
  );
};

export default App;
```
![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365084/5ad31944-d487-b8e1-1f74-1b80c50f3f35.gif)

## scalable（ドラッグでスケール変更できる）

```jsx
import React, { useState, useEffect } from 'react';
import Moveable from 'react-moveable';
import './App.css';

const App = () => {
  const [target, setTarget] = useState(null);

  useEffect(() => {
    setTarget(document.querySelector('.scalable'));
  }, []);

  return (
    <React.Fragment>
      <div className={'moveable scalable'}>scalable</div>
      <Moveable
        target={target}
        scalable={true}
        origin={false}
        throttleScale={0}
        keepRatio={true}
        onScale={e => {
          e.target.style.transform = e.transform;
        }}
      />
    </React.Fragment>
  );
};

export default App;
```
![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365084/b45a3efc-2140-1d7d-2b42-0aac9edcf5a0.gif)

## rotatable（ドラッグで回転できる）

```jsx
import React, { useState, useEffect } from 'react';
import Moveable from 'react-moveable';
import './App.css';

const App = () => {
  const [target, setTarget] = useState(null);

  useEffect(() => {
    setTarget(document.querySelector('.rotatable'));
  }, []);

  return (
    <React.Fragment>
      <div className={'moveable rotatable'}>rotatable</div>
      <Moveable
        target={target}
        rotatable={true}
        origin={false}
        throttleRotate={0}
        onRotate={e => {
          e.target.style.transform = e.transform;
        }}
      />
    </React.Fragment>
  );
};

export default App;
```
![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365084/a0a17e7e-eb5a-8771-25a8-d756d3a1242d.gif)

## warpable（ドラッグで歪ませる）

```jsx
import React, { useState, useEffect } from 'react';
import Moveable from 'react-moveable';
import './App.css';

const App = () => {
  const [target, setTarget] = useState(null);

  useEffect(() => {
    setTarget(document.querySelector('.warpable'));
  }, []);

  return (
    <React.Fragment>
      <div className={'moveable warpable'}>warpable</div>
      <Moveable
        target={target}
        warpable={true}
        origin={false}
        throttleRotate={0}
        onWarp={e => {
          e.target.style.transform = e.transform;
        }}
      />
    </React.Fragment>
  );
};

export default App;
```
![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365084/5f7e5df7-d297-dab3-6875-faccbdb41153.gif)

## 組み合わせる（draggable & scalable & rotatable）

```jsx
import React, { useState, useEffect } from 'react';
import Moveable from 'react-moveable';
import './App.css';

const App = () => {
  const [target, setTarget] = useState(null);

  useEffect(() => {
    setTarget(document.querySelector('.mix'));
  }, []);

  return (
    <React.Fragment>
      <div className={'moveable mix'}>mix</div>
      <Moveable
        target={target}
        draggable={true}
        scalable={true}
        rotatable={true}
        origin={false}
        throttleRotate={0}
        onDrag={e => {
          e.target.style.transform = e.transform;
        }}
        onScale={e => {
          e.target.style.transform = e.transform;
        }}
        onRotate={e => {
          e.target.style.transform = e.transform;
        }}
      />
    </React.Fragment>
  );
};

export default App;
```
![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365084/a51daf35-3e3b-02f7-6456-ffe52b9afaf8.gif)

# まとめ

それっぽい動きはできた。
モバイルのピンチインにも対応してるみたいですが、未確認です。