LoginSignup
5
4

More than 1 year has passed since last update.

Fabric.jsのオブジェクトとVueの連係

Last updated at Posted at 2022-03-26

はじめに

Fabric.js本体やVueのリアクティブな機能を連係する実装について試した。

準備

プロジェクト作成

vue createでVue 3 の Composition API を TypeScript で記述するように選択してプロジェクトを作成。

Lintルール調整

個人的な好みによりLintルールを調整。

本記事で前提となるLintルール
.eslint.js
  // comma-dangleとsemiを追加
  rules: {
    'comma-dangle': ['error', {
      arrays: 'always-multiline',
      objects: 'always-multiline',
      imports: 'never',
      exports: 'never',
      functions: 'never',
    }],
    semi: ['error', 'always'],
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  },

App.vue更新

プリセットのコンポーネントとスタイルは除去して検証用のコンポーネントのみにする。

更新後のApp.vue
App.vue
<template>
  <MyCanvas/>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import MyCanvas from '@/components/MyCanvas.vue';

export default defineComponent({
  name: 'App',
  components: {
    MyCanvas,
  },
});
</script>

MyCanvas.vueについては後述

検証

Fabric.jsインストール

fabric本体と型情報をそれぞれ npm install する。

npm install fabric
npm install --save-dev @types/fabric

Fabric.jsによる描画

まずは チュートリアル に従い四角形を配置してみる。
※下記ではチュートリアルに従いidによりcanvasを決定しているが、Vueでやるならテンプレート参照を使ったほうがよいかも(後述)

MyCanvas.vue
<template>
  <canvas id="myCanvas"></canvas>
</template>

<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import { fabric } from 'fabric';

export default defineComponent({
  setup: () => {
    // create a rectangle object
    const rect = new fabric.Rect({
      left: 200,
      top: 100,
      fill: 'red',
      width: 31,
      height: 31,
    });

    onMounted(() => { // ★1
      // create a wrapper around native canvas element
      const canvas = new fabric.Canvas('myCanvas');

      // "add" rectangle onto canvas
      canvas.add(rect);
    });
  },
});
</script>

<style>
.canvas-container { /* ★2 */
  border: 1px solid grey;
}
</style>
  • ★1: fabric.Canvas() はDOM操作を伴うため onMounted 内で実行する必要がある
  • ★2: fabricによりcanvas要素がdiv要素によってwrapされる(.canvas-container)のでそれにスタイルを定義している

実行結果は以下の通り。
image.png

デフォルトで移動・回転・拡大縮小ができるようになっている。
Animation1.gif

Elementで指定するパターン

Canvasのコンストラクタ引数にはidだけでなくHTMLCanvasElementでの指定もできる。テンプレート参照により取得したElementで指定した方がVueらしい記述になる。

setupの抜粋(id指定パターンとの差異)
+   const canvasElm = ref<HTMLCanvasElement>();
    onMounted(() => {
      // create a wrapper around native canvas element
-     const canvas = new fabric.Canvas('myCanvas');
+     const canvas = new fabric.Canvas(canvasElm.value!);

      // "add" rectangle onto canvas
      canvas.add(rect);
    });

+   return {
+     canvasElm,
+   };
templateの抜粋(id指定パターンとの差異)
- <canvas id="myCanvas"></canvas>
+ <canvas ref="canvasElm"></canvas>

画像の表示と注意点

fabric.Imageを使用する場合は元となるimg要素が必要になるのでfabric.Canvasと同様にonMountedでインスタンスを作成する。

setupの抜粋
+   const img1Elm = ref<HTMLImageElement>();
+   const img2Elm = ref<HTMLImageElement>();
    const canvasElm = ref<HTMLCanvasElement>();
    onMounted(() => {
      // create a wrapper around native canvas element
      const canvas = new fabric.Canvas(canvasElm.value!);

      // "add" rectangle onto canvas
      canvas.add(rect);

+     const img1 = new fabric.Image(img1Elm.value!, { left: 10, top: 20, scaleX: 0.4, scaleY: 0.4 });
+     const img2 = new fabric.Image(img2Elm.value!, { left: 60, top: 60, scaleX: 0.4, scaleY: 0.4 });
+     canvas.add(img1, img2);
    });

    return {
      canvasElm,
+     img1Elm,
+     img2Elm,
    };
templateの抜粋
+ <img ref="img1Elm" src="@/assets/sun.png" class="hidden">
+ <img ref="img2Elm" src="@/assets/moon.png" class="hidden">
  <!-- class="hidden"は当該img要素自体を画面表示させないために付与 -->

ただし、上記の記述のみではbase64にコンパイルされた画像にのみ有効だがコンパイルされてない画像はうまく表示することができない。Vueのデフォルト設定ではassetsに配置したとしても一定以上のサイズの画像はコンパイルされずにpublicと同様の扱いになるため注意が必要。

実行結果は以下の通り。(初期状態ではsun.pngが表示されず、次のレンダリングが走ったタイミングで反映される)(←moon.pngはコンパイルされ、sun.pngはコンパイルされていない)
Animation4.gif

これを防ぐためには以下のいずれかの処理が必要になる

  • コンパイルされるようにwebpackの設定を変更する
  • v-on:loadを使用して画像の読み込み完了を検知してからインスタンスをaddする(またはrenderAllなどで再レンダリングする)
  • 画像をpublic配下に置き、img要素経由ではなくfabric.Image.fromURLから作成する

参考

オブジェクトのプロパティについて

オブジェクトのコンストラクタ引数には任意のプロパティを設定可能。(公式ドキュメント:Rect参照)

    const rect = new fabric.Rect({
      left: 200,
      top: 100,
      fill: 'red',
      width: 31,
      height: 31,
+     lockRotation: true,
    });

また、インスタンスを経由して後から参照・設定することも可能。

template部分
<button @click="square">正方形にする</button>
script部分
    const square = () => {
      rect.set('scaleY', rect.scaleX);
      canvas.renderAll();
    };
※上では省略しているがcanvasの宣言をomMountedの外に出している
setup全文
  setup: () => {
    // create a rectangle object
    const rect = new fabric.Rect({
      left: 200,
      top: 100,
      fill: 'red',
      width: 31,
      height: 31,
      lockRotation: true,
    });

    let canvas: fabric.Canvas;
    onMounted(() => {
      // create a wrapper around native canvas element
      canvas = new fabric.Canvas('myCanvas', { width: 500, height: 300 });

      // "add" rectangle onto canvas
      canvas.add(rect);
    });

    const square = () => {
      rect.set('scaleY', rect.scaleX);
      canvas.renderAll();
    };

    return {
      square,
    };
  },

Canvasについてもコンストラクタの第2引数に任意のプロパティを設定可能。(公式ドキュメント:Canvas参照)

      canvas = new fabric.Canvas('myCanvas', { width: 500, height: 300 });

実行結果は以下の通り。(回転不可、移動・拡大/縮小は可能、ボタン押下で拡大率を揃える)
Animation2.gif

Vueとの連係

script部分
    const rect = new fabric.Rect({
      left: 200,
      top: 100,
      fill: 'red',
      width: 31,
      height: 31,
-     lockRotation: true,
+     hasControls: false, // ◆1
    });
    // ★2
+   const rectPos = reactive({ x: rect.getCenterPoint().x, y: rect.getCenterPoint().y });
+   rect.on('moving', () => { // ★3
+     rectPos.x = rect.getCenterPoint().x;
+     rectPos.y = rect.getCenterPoint().y;
+   });
template部分
<template>
  <canvas id="myCanvas"></canvas>
  <div>
-   <button @click="square">正方形にする</button>
+   (x, y) = ({{rectPos.x}}, {{rectPos.y}})
  </div>
</template>
  • ◆1: 拡大/縮小が絡むとややこしいのでコントロールを出さないようにしておく
  • ★2: リアクティブオブジェクトを作成する
  • ★3: Fabric側の機能であるイベント購読機能を利用する(利用可能なイベントはRectおよびそのスーパークラスObject参照)

実行結果は以下の通り。
Animation3.gif

参考

5
4
1

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
5
4