JavaScript
angular
fabric.js

AngularとFabric.jsでお絵描き

Angularアプリでお絵描きする方法です。
HTMLのcanvasをJavascriptから扱えるFabric.jsというライブラリがあったので、これと組み合わせて実現してみました。
Fabric.jsは生のcanvasを扱うよりも簡単にcanvasの操作を行うことが出来る他、canvasオブジェクトのレイヤー機能や拡大縮小、回転などと言った操作が簡単に行えるので、かなり便利です。

完成形
https://daikiojm.github.io/angular-fabricjs-freehand-sample/

環境

$ ng --version
@angular/cli: 1.4.1
node: 8.1.3
os: darwin x64

上記バージョンのangular-cliで作成した、angularプロジェクトが対象です。

手順

Angular Materialの導入

ボタンなどをいい感じにスタイリングするために、Angular Materialを導入しました。以前にAngularCLIで作成したプロジェクトにAngular Materialを導入する手順をQiitaに書いたのでここを参考に作業を行います。

注意

この記事を書いている時点の最新版 2.0.0-beta.11では、MaterialModuleが廃止されているので、Angular Materialのうち使用するコンポーネントに対応するモジュールを個別にインポートする必要があります。
https://github.com/angular/material2/pull/6803

今回は、ボタンとチェックボックスをいい感じにスタイリングしたかったので、次のモジュールをインポートしました。

MdButtonModule
MdCheckboxModule

Fabric.jsの導入

npmからインストールして、使用するコンポーネント内でimportします。

Fabric.js のoptionalDependenciesとしてインストールされるnode-canvasのインストールになかなか時間がかかるので、今回は省略するために--no-optionalを付けています。

npm install --no-optional --save fabric

今回は、app.componentに直接実装するので、ここに記述

app.component.ts
import { fabric } from 'fabric';

説明

canvasの作成

まずは、Viewテンプレート側でhtmlのcanvasを作成します。

<canvas id="canvas" width="600" height="600"></canvas>

app.componentにcanvasオブジェクトを宣言します。

  private canvas: any;

次に、ngOnInit 内にcanvasの初期化処理を書いていきます。
isDrawingModeオプションをつけることで、手書き線の描画が可能になります。

    this.canvas = new fabric.Canvas('canvas', {
      isDrawingMode: true,
      selection: true,
      stateful: true
    });

ブラシのセットアップ

前の手順までで、canvasへの手書き描画が出来るようになっていますが、線の色や太さなどの細かい設定を行います。

    this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas);
    this.canvas.freeDrawingBrush.color = 'red';
    this.canvas.freeDrawingBrush.width = 5;
    this.canvas.freeDrawingBrush.shadowBlur = 0;
    this.canvas.hoverCursor = 'move';

Undo/Redo機能

Fabric.jsでは、canvasオブジェクトをJavascriptのオブジェクトとして一つ一つ個別に操作することが出来るため、履歴操作も簡単に行うことが出来ました。

app.componentにcanvasHistoryを宣言します。

  private canvas: any;

Fabric.jsではcanvasに対するイベントを簡単に取得することが出来るため、次のようにオブジェクトの追加を監視することが出来ます。

    this.canvas.on('object:added', (e) => {
      const object = e.target;
      if (!this.isRedoing) {
        this.canvasHistory = [];
      }
      this.isRedoing = false;
    });

「戻る」「進む」ボタンが押されたときの操作は次のようになります。

  // 戻る
  undo() {
    if (this.canvas._objects.length > 0) {
      const undoObject = this.canvas._objects.pop();
      this.canvasHistory.push(undoObject);
      this.canvas.renderAll();
    }
  }

  // 進む
  redo() {
    if (this.canvasHistory.length > 0) {
      this.isRedoing = true;
      const redoObject = this.canvasHistory.pop();
      this.canvas.add(redoObject);
    }
  }

モード切替

消しゴム機能の実装を考えましたが、生のcanvasオブジェクトを操作しなければ出来ないようなので、今回は見送りました。
その代わり、次のように削除機能を実装しました。

Fabric.jsでは、一つ一つのオブジェクトをマウス操作で移動、拡大縮小、回転操作することが出来ますが、手書き描画が出来る状態ではそれらが行えないので、まずはそれらを切り替えるボタンを作成します。

Fabric.jsのcanvasオブジェクトのプロパティ``にViewテンプレート側からもアクセスできるよう、getter/setterを作成しました。

app.component.ts
  get drawingMode(): boolean { return this.canvas.isDrawingMode; }
  set drawingMode(val: boolean) { this.canvas.isDrawingMode = val; }

Viewコンポーネント側からはmd-checkboxを使ってモードの切替をチェックボックスのトグルで行えるようにしました。

app.component.html
  <md-checkbox class="example-margin" [(ngModel)]="drawingMode">手書きモード</md-checkbox>

また、「手書きモード」がオフの状態では選択されたcanvasオブジェクトを削除できる機能を作成しました。次のようなメソッドを用意して、Viewテンプレート側からボタンの(click)イベントで呼び出すだけです。

app.component.ts
  deleteObject() {
    const object = this.canvas.getActiveObject();
    this.canvas.remove(object);
  }

所感

生のcanvasを直接操作するよりかなり楽に実現できたと思います。
今回の内容では、あまりAngularと関係ない内容でしたが、機能を拡張して矩形オブジェクトの配置やペンの色変更など複雑な操作が増えるにつれてAngularと組み合わせるメリットが出てくるのでは無いかと思います。

リポジトリ

https://github.com/daikiojm/angular-fabricjs-freehand-sample