Fabric.jsでお絵かきアプリをつくる

この記事は [C3 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/c3) 14日目の記事です

# はじめに

ふぃるです。
C3 では Web フロントエンドを中心で色々開発したり、曲を作ったりしています。
最近、原神にはまって卒業研究がなかなか進みません。助けてください。

# Fabric.jsとは

その名の通り、JavaScript のライブラリです。
お絵かきをすることができます。
使い方としては、お題をもとにどんな絵を書いてるか当てるゲームや、お絵かき伝言ゲームなどがあります。なんか聞いたことがあるゲームだって？気のせいじゃないですか？

公式のホームページは[こちら](http://fabricjs.com/)
Tutorial や Docs などがありますが微妙に見づらいので、ここで色々紹介します。

# デモ

React + TypeScript + MUI でデモを作りました。
https://handwritten-app.vercel.app/

Github の URL も一応載せておきます。
`git clone` して煮るなり焼くなり好きにしてもらっても大丈夫です。
https://github.com/citrus-candy/handwritten-app

デモでは以下の機能を実装しています。

- Undo & Redo
- Clear
- ブラシの色変更
- ブラシの太さ変更

この機能について、どのような実装をしているのか紹介していきます。

# Undo & Redo

Undo と Redo に関しては Fabric.js に実装されていないので、自前で実装する必要があります。

## Undo

```typescript
const undo = () => {
  if (canvas !== undefined && canvas._objects.length > 0) {
    const copyArray = [...canvasHistory]
    copyArray.push(canvas._objects.pop()!)
    canvasHistory = copyArray
    canvas.renderAll()
  }
}
```

`canvas._objects` には今までに書いた線がオブジェクトとして保存されているので、それを利用します。

## Redo

```typescript
useEffect(() => {
  canvas?.on('object:added', () => {
    if (!isRedoing) {
      canvasHistory = []
    }
    isRedoing = false
  })
}, [canvas])

const redo = () => {
  if (canvas !== undefined && canvasHistory.length > 0) {
    isRedoing = true
    canvas.add(canvasHistory.pop()!)
  }
}
```

`canvas.add` を使って、Undo した線をもとに戻します。
> `add(...object) → {self}`
> Adds objects to collection, Canvas or Group, then renders canvas (if `renderOnAddRemove` is not `false`). in case of Group no changes to bounding box are made. Objects should be instances of (or inherit from) fabric.Object Use of this function is highly discouraged for groups. you can add a bunch of objects with the add method but then you NEED to run a addWithUpdate call for the Group class or position/bbox will be wrong.
> **Parameters:**
> | Name | Type | Attributes | Description |
> | - | - | - | - |
> | object | fabric.Object | < repeatable > | Zero or more fabric instances |
> 
> **Returns:**
> thisArg
> Type: Self
> 
> http://fabricjs.com/docs/fabric.Canvas.html#add

また、`object:added` で線を書くイベントを取得することができます。
> **Object related**
> - object:added — fired after object has been added
> 
> https://github.com/fabricjs/fabric.js/wiki/Working-with-events

# Clear

```typescript
const clear = () => {
  if (canvas !== undefined) {
    canvas.clear()
    canvasHistory = []
    canvas.setBackgroundImage(bgImageUrl, () => canvas.renderAll())
  }
}
```

`canvas.clear()` でキャンバスを初期化します。
> `clear() → {fabric.Canvas}`
> Clears all contexts (background, main, top) of an instance
> **Returns:**
> thisArg
> Type: fabric.Canvas

また、デモでは使っていませんが `canvas.setBackgroundImage()` でキャンバスに背景画像を設定することができます。
> `setBackgroundImage(image, callback, optionsopt) → {fabric.Canvas}`
> Sets background image for this canvas
> **Parameters:**
> | Name | Type | Attributes | Description |
> | - | - | - | - |
> | image | fabric.Image or String |  | fabric.Image instance or URL of an image to set background to |
> | callback | function |  | Callback to invoke when image is loaded and set as background |
> | options | Object | < optional > | Optional options to set for the background image. |
> 
> **Returns:**
> thisArg
> Type: fabric.Canvas
> 
> http://fabricjs.com/docs/fabric.Canvas.html#setBackgroundImage


# 線の色の変更

```typescript
useEffect(() => {
  if (canvas !== undefined) {
    canvas.freeDrawingBrush.color = selectColor
  }
}, [canvas, selectColor])
```

`canvas.freeDrawingBrush.color` の値を変更することで、線の色を変えることができます。
> `color :String`
> Color of a brush
> **Type:**
> - String
> 
> http://fabricjs.com/docs/fabric.BaseBrush.html#color


# 線の太さの変更

```typescript
useEffect(() => {
  if (canvas !== undefined) {
    canvas.freeDrawingBrush.width = brushwidth
  }
}, [canvas, brushwidth])
```

`canvas.freeDrawingBrush.width` の値を変更することで、線の太さを変えることができます。
> `width :Number`
> Width of a brush, has to be a Number, no string literals
> **Type:**
> - Number
> 
> http://fabricjs.com/docs/fabric.BaseBrush.html#width

# おわりに

Fabric.js を使えば、お絵かきが簡単に実装できます。
他にも、キャンバスのオブジェクトを JSON でエクスポートしたり、逆にインポートができたりします。
> - [toJSON](http://fabricjs.com/docs/fabric.Canvas.html#toJSON)
> - [loadFromJSON](http://fabricjs.com/docs/fabric.Canvas.html#loadFromJSON)

明日は、西園寺やきしゃもさんの「C言語におけるポインタの文法について」です！