【備忘録】逆引きFabric.js

# はじめに

Fabric.jsの機能で個人的に試したものを備忘録として残す。後日試した機能があれば追記する(かも)。

検証にはVue環境を使用している(下記参照)。  
※Fabric.js本体の機能なのでF/Wには依存しない(はず)

https://qiita.com/kurukuruz/items/04866272867f4ace3a17

# 実装
## 選択時の□(拡大/縮小・回転するやつ)を出さない

対象Objectの`hasControls`を`false`にする。

```html:素体となるcanvas
<canvas id="theCanvas"></canvas>
```
```ts:描画スクリプト
const rect1 = new fabric.Rect({
  left: 100,
  top: 50,
  width: 31,
  height: 31,
  fill: 'red',
  hasControls: false, // ★
});
const canvas = new fabric.Canvas('theCanvas');
canvas.add(rect1);
```
![コントロール非表示.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/c4e5581d-895e-3c86-ab2f-5bb5be13d0a6.gif)

## 選択時に前に出さない

Canvasの`preserveObjectStacking`を`true`にする。

```ts
const rect1 = new fabric.Rect({/*赤*/});
const rect2 = new fabric.Rect({/*青*/});
const canvas = new fabric.Canvas('theCanvas', { preserveObjectStacking: true }); // ★
canvas.add(rect1, rect2);
```
![選択時に前に出さない.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/2679cf66-eda8-42c5-4677-4485ce753e3e.gif)

## 後から追加するオブジェクトを背面に配置する

`Canvas.insertAt()`を使用する。

- 第3引数が`false`の場合は挿入、`true`の場合は置換される
- 挿入の場合、第2引数のインデックスに配置され、元々インデックスが引数値以上のものは後ろにシフトされる
- インデックスが大きいものが前面に表示される

```ts
const rect1 = new fabric.Rect({/*赤*/});
const rect2 = new fabric.Rect({/*緑*/});
const rect3 = new fabric.Rect({/*青*/});
const canvas = new fabric.Canvas('theCanvas', { preserveObjectStacking: true });
canvas.add(rect1, rect2);
canvas.insertAt(rect3, 1, false); // ★
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/10f0f1be-d2db-9b73-28fe-fa7821f95312.png)

## 前面／背面を後から変更する

変更対象のObjectインスタンスのメソッドを利用する。

- 最前面にする場合は`bringToFront()`
- 最背面にする場合は`sendToBack()`
- 任意のインデックスにする場合は`moveTo()`（対象Objectが引数のインデックスに配置され、元々インデックスが引数値以上のものは後ろにシフトされる）

```ts:クリックイベント定義
// 初期描画までは上と同じ
const frontRed = () => { rect1.bringToFront(); };
const frontGreen = () => { rect2.bringToFront(); };
const frontBlue = () => { rect3.bringToFront(); };
const backRed = () => { rect1.sendToBack(); };
const backGreen = () => { rect2.sendToBack(); };
const backBlue = () => { rect3.sendToBack(); };
const midRed = () => { rect1.moveTo(1); };
```
```vue:onClick割当
<button @click="frontRed">赤を最前面</button>
<button @click="frontGreen">緑を最前面</button>
<button @click="frontBlue">青を最前面</button>
<br>
<button @click="backRed">赤を最背面</button>
<button @click="backGreen">緑を最背面</button>
<button @click="backBlue">青を最背面</button>
<br>
<button @click="midRed">赤を真ん中</button>
```
![前面背面制御.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/8652049c-684b-b97d-c964-3a0e10334c8c.gif)

## オブジェクトの端の座標や中心座標を取得する

オブジェクトの`getPointByOrigin`メソッドを利用する。

- 第一引数(`originX`): `'left' | 'center' | 'right'` (それぞれ左端・左右中心・右端)
- 第二引数(`originY`): `'top' | 'center' | 'bottom'` (それぞれ上端・上下中心・下端)

また、左右・上下とも中心の座標を取得する場合は`getCenterPoint`メソッドでもよい。

```ts: 座標取得
      // rect1の定義は上と同じ
      const lt = rect1.getPointByOrigin('left', 'top');
      leftTopPos.x = lt.x;
      leftTopPos.y = lt.y;

      const rb = rect1.getPointByOrigin('right', 'bottom');
      rightBtmPos.x = rb.x;
      rightBtmPos.y = rb.y;

      const center = rect1.getCenterPoint();
      centerPos.x = center.x;
      centerPos.y = center.y;
```
```vue:座標を画面に表示
  <div>
    left-top = ({{leftTopPos.x}}, {{leftTopPos.y}})
  </div>
  <div>
    center = ({{centerPos.x}}, {{centerPos.y}})
  </div>
  <div>
    right-bottom = ({{rightBtmPos.x}}, {{rightBtmPos.y}})
  </div>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/acf2462c-ca23-6ac2-f814-acbcc543e430.png)
※設定してるサイズより大きい気がする…枠線分が入ってる？？

## オブジェクトの端や中心を基準座標として配置する

オブジェクトを作成する際のオプション`originX`, `originY`を設定するとその点が基準座標になる。

- `originX`: `'left' | 'center' | 'right'` (それぞれ左端・左右中心・右端)
- `originY`: `'top' | 'center' | 'bottom'` (それぞれ上端・上下中心・下端)

座標を指定するプロパティ名は`left`,`top`だが上で指定した点に合うように配置されるようになる。

```ts
    // 座標・サイズは同じ値を使用
    const posAndSize = {
      left: 100,
      top: 50,
      width: 31,
      height: 31,
    };
    const rect1 = new fabric.Rect({ // 赤・デフォルト
      ...posAndSize,
      fill: 'red',
      hasControls: false,
    });
    const rect2 = new fabric.Rect({ // 緑・上下左右中心
      ...posAndSize,
      originX: 'center', // ★
      originY: 'center', // ★
      fill: 'green',
      hasControls: false,
    });
    const rect3 = new fabric.Rect({ // 青・右下端
      ...posAndSize,
      originX: 'right',  // ★
      originY: 'bottom', // ★
      fill: 'blue',
      hasControls: false,
    });
    // 見やすさの都合で緑→赤→青の順に追加
    canvas.add(rect2, rect1, rect3);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/50160b64-d775-1762-2f95-084cbc5b03bb.png)
※十字部分が基準座標`(100, 50)`

## 枠線を表示する

`stroke`および`strokeXXX`という名前のプロパティを設定する。

また、オブジェクトの色(`fill`)はデフォルトで黒(`'rgb(0,0,0)'`)なので、枠線のみ表示させる場合は`fill`を透明(`'transparent'`, `'rgba(0,0,0,0)'`など)にする必要がある。

```ts
   // rect1, rect2は上と同じ
   const rect3 = new fabric.Rect({
      ...posAndSize,
      originX: 'right',
      originY: 'bottom',
      fill: 'transparent', // 中身の透明化
      stroke: 'blue', // 枠線の色
      strokeWidth: 4, // 枠線の幅
      hasControls: false,
    });
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/b3673925-7ed1-8517-0818-4a14aad7e1ea.png)

### 注意事項: 枠線表示時の座標とサイズについて

基準座標が端にある場合、枠線の外端が基準座標に合うように配置されるが、中身(設定しているサイズの図形)の端が枠線の中心に来るように設定される。（枠線を太い点線にするとわかりやすい）

```ts
    const rect1 = new fabric.Rect({
      left: 100,
      top: 50,
      width: 80,
      height: 80,
      fill: 'red',
      stroke: 'blue',
      strokeWidth: 10,
      strokeDashArray: [15, 10],
      hasControls: false,
    });
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266076/a424ac47-e662-0943-6963-0ed924d69db9.png)
※十字部分が基準座標`(100, 50)`

# 参考

http://fabricjs.com/docs/