canvas
vue.js
konva

Vue.js with canvas

 vue.js の Canvas Library

vue.js の案件を最近受けていまして、そこで canvas を使って署名などをかけるコンポーネントが欲しいとの依頼を受けました。

普通にライブラリなどは使わずに canvas で実装しても良かったのですができるなら楽をしたくてライブラリを調べて見ました。

vue.js で vue.js canvas 検索するとできてきたのが vue konva です。

この記事を書いてる時点ではこれ以外のライブラリはなさそうでした。

konva vue は、canvas Library として 実績のある konva を vue.js 対応させたものです。

  • konva vue v1.0.2
  • konva 2.1.3

konva について

konva は creat.js など似ていて特徴としては以下のようなことができます。

  • canvas に stage, layer, shape といった形で階層を作って描画できる
  • それぞれのオブジェクトにクリックイベントの設定やスタイルの適用ができる

階層の概念はコードで表すとこんな感じです。

// first we need to create a stage
var stage = new Konva.Stage({
  container: 'container',   // id of container <div>
  width: 500,
  height: 500
});

// then create layer
var layer = new Konva.Layer();

// create our shape
var circle = new Konva.Circle({
  x: stage.getWidth() / 2,
  y: stage.getHeight() / 2,
  radius: 70,
  fill: 'red',
  stroke: 'black',
  strokeWidth: 4
});

// add the shape to the layer
layer.add(circle);

// add the layer to the stage
stage.add(layer);

konva vue ができること

vue.js は konva の階層を template で表現することができます。

<template>
  <v-stage :config="configKonva">
    <v-layer>
      <v-circle :config="configCircle"></v-circle>
    </v-layer>
  </v-stage>
</template>
<script>
export default {
  data() {
    return {
      configKonva: {
        width: 200,
        height: 200
      },
      configCircle: {
        x: 100,
        y: 100,
        radius: 70,
        fill: "red",
        stroke: "black",
        strokeWidth: 4
      }
    };
  }
};

</script>

template に書けるのはすごい便利です。
描画した  shape に vue っぽく イベントを設定することができます。

<template>
<v-group>
  <v-circle 
    :config='handler' 
    ref='handler'
    @mouseover='onHandlerMouseOver'
    @mouseout='onHandlerMouseOut'
    @mousedown='onHandlerMouseDown'
    @dragmove='onHandlerDragMove'
    @dragend='onHandlerDragEnd'
    @mouseup='onHandlerMouseUp'>
  </v-circle>   
</v-group>

</template>

こんな感じで hanlder method を書いておくと コールしてくれます。

konva vue が苦手そうなこと

今回は、手書きなので line をどんどん canvas に追加する必要があります。
example に動的に shape を増やしているものを探して見たのですがありませんでした。

また canvas を操作する context へのアクセスも現状では API 経由ではできませんでした。(version 1.0.2)

http://rafaelescala.com/vue-konva-doc/#api

shape が動的に増減するケースのカバーがいまのところあまりできてなさそうだったので、今回は、konva vue を使うのをやめました。

vue.js で konva を使う

最終どうしたかというと、konva vue はやめて konva 単体で使うことにしました。

流れ的にはこんな感じです。

  1. template に canvas を定義
  2. マウント後に konva を生成して stage, layer などを作りイベントを設定する
  3. マウス、タッチイベントに合わせて canvas の context から Line の描画を行う。

konva vue を使っていないので、 stage や layer は mount 時に追加する必要があります。

ただ canvas を template に書くことができるようになったことにより ref を設定してキャンバスの context などにアクセスできるようになりました。

<template>
  <div ref="container">
    <canvas ref="canvas"></canvas>
  </div>

</template>

<script>
import Konva from "konva";

export default {
  name: "FreeDrawing",
  mounted() {
    this.canvas = this.$refs.canvas;
    this.context = this.canvas.getContext("2d");

    this.context.strokeStyle = this.paintStyle.color;
    this.context.lineJoin = "round";
    this.context.lineWidth = this.paintStyle.lineWidth;

    const container = this.$refs.container;

    this.stage = new Konva.Stage({
      container,
      width: this.width,
      height: this.height
    });

    //背景レイヤー
    this.bgLayer = new Konva.Layer();

    //書き込みレイヤー
    this.drawingLayer = new Konva.Layer();

    this.stage.add(this.bgLayer);
    this.stage.add(this.drawingLayer);

    this.backGround = new Konva.Image({
      image: this.canvas,
      x: 0,
      y: 0
    });

    this.drawScope = new Konva.Image(
      Object.assign(
        {
          image: this.canvas
        },
        this.bgConfig
      )
    );

    this.bgLayer.add(this.backGround);
    this.drawingLayer.add(this.drawScope);
    this.stage.draw();

    //イベントの設定
    this.stage.on("contentMousedown.proto", this.mousedown);
    this.stage.on("contentTouchstart.proto", this.mousedown);

    this.stage.on("contentMouseup.proto", this.mouseup);
    this.stage.on("contentTouchend,.proto", this.mouseup);

    this.stage.on("contentMousemove.proto", this.mousemove);
    this.stage.on("contentTouchmove.proto", this.mousemove);
  },
  data() {
    return {
      stage: {},
      canvas: {},
      context: {},
      isPaint: false,
      backGround: {},
      lastPointerPosition: {}
    };
  },
  methods: {
    mousedown: function() {
      this.isPaint = true;
      this.lastPointerPosition = this.stage.getPointerPosition();
    },
    mouseup: function() {
      this.isPaint = false;
    },
    mousemove: function() {
      if (!this.isPaint) {
        return;
      }

      if (this.mode === "eraser") {
        this.context.globalCompositeOperation = "destination-out";
      } else {
        this.context.globalCompositeOperation = "source-over";
      }

      this.context.beginPath();

      let localPos = {
        x: this.lastPointerPosition.x - this.backGround.x(),
        y: this.lastPointerPosition.y - this.backGround.y()
      };

      this.context.moveTo(localPos.x, localPos.y);

      const pos = this.stage.getPointerPosition();
      localPos = {
        x: pos.x - this.backGround.x(),
        y: pos.y - this.backGround.y()
      };

      this.context.lineTo(localPos.x, localPos.y);
      this.context.closePath();
      this.context.stroke();

      this.lastPointerPosition = pos;
      this.drawingLayer.draw();

    }
  }
};
</script>

といった感じになりました。

ただ vue konva も API 経由で context を取得できたり、動的に shape を追加するサンプルが増えると使いやすくなるかなと思いました。

時間があれば要望をあげたり、pr したりしようと思います。

ただ現状でも template でほとんど実装できてしまうのでかなり便利だと思います。