tinyshare微博账号
用TypeScript和Vue实现可交互的Canvas元素 一月 29, 2019

在vue中可以通过「v-*」来定义html标签的属性,但是canvas属性不能直接接收。作者本以为这是一个很简单的问题,但是最后却饶了很多路才解决,并放出自己的解决方案。

作者使用Vue中的directive(指令)来解决canvas元素与外界ViewModel保持数据同步的操作。自定义指令可以让我们为自己的template(模板)使用v-something,并定义自己的行为。

一下是以单文件类组件为例的一段示例代码,且使用TypeScript语法。

<template>
  <div class="rxcanvas">
    <span>{{ size }}</span>
    <input type="range" min="1" max="100" step="5" id="size" v-model="size">
    <label for="size">- Size</label>
    <p><canvas></canvas></p>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import Dot from "@/dot"; // defined below

@Component
export default class RxCanvas extends Vue {
  private data() {
    return {
      size: 10
    };
  }

  // computed property
  get dot(): Dot {
    return new Dot(this.$data.size);
  }
}
</script>

<style scoped>
</style>

Dot类可以将自己将canvas元素会知道一个对象上。

// dot.ts
export default class Dot {
  private readonly color: string = "#000";
  constructor(private radius: number) { }
  public draw(canvas: HTMLCanvasElement): void {
    // resize canvas to dot size
    const canvasDim = this.radius * 2;
    canvas.width = canvasDim;
    canvas.height = canvasDim;

    // get context for drawing
    const ctx = canvas.getContext('2d')!;

    // start with a blank slate
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // find the centerpoint
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;

    // create the shape
    ctx.beginPath();
    ctx.arc(centerX, centerY, this.radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.stroke();
  }
}

在这之后,将这个自定义指令命名为 draw(你可以命名成任意的名字),我们将dot传入,dot在RxCanvas类中定义的计算属性,只要size变化,这个计算属性会创建一个有正确size的新的Dot。

自定义指令在Vue组件内定义,如果使用了vue-property-decorator,可以使用以下方式定义:

@Component({
  directives: {
    "draw": function(canvasElement, binding) {
    // casting because custom directives accept an `Element` as the first parameter
      binding.value.draw(canvasElement as HTMLCanvasElement);
    }
  }
})
export default class RxCanvas extends Vue {
    // data(), dot(), etc
}

要想让指令在属性发生变化时,重新绘制,代码需要改成如下这样。

directives: {
    draw: {
      bind: function(canvasElement: Element, binding: VNodeDirective) {
        binding.value.draw(canvasElement as HTMLCanvasElement);
      },
      update: function(canvasElement, binding) {
        binding.value.draw(canvasElement as HTMLCanvasElement);
      }
    }
}

bind函数只会在组件被创建的时候触发,update函数会在RxCanvas类创建的VNode示例发生变化时触发,包括data的变化。

如果想要在项目全局内使用这个自定义指令,需要将它定义在全局下,如下:

// index.ts
Vue.directive('draw': function(canvasElement, binding) {
      binding.value.draw(canvasElement as HTMLCanvasElement);
});

希望这篇文章能给你启发,让你更好地运用Vue的自定义指令来创建更加丰富的可复用组件。