開発者ドキュメント

Vue.jsでHTML5キャンバスを制御する方法

序章

ほとんどの場合、DOMを介してWebページと対話するVue.jsコンポーネントを作成します。 しかし、Vueの反応性システムはそれ以上に役立ちます!

この記事では、HTML5キャンバスでVueを使用して基本的な棒グラフをレンダリングするための一連のコンポーネントを作成します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、ノードv16.5.0、npm v7.20.0、およびvuev2.6.11で検証されました。

ステップ1—プロジェクトの設定

このプロジェクトは@vue /cliで開始できます。

  1. npx @vue/cli create vue-canvas-example --default

次に、新しいプロジェクトディレクトリに移動します。

  1. cd vue-canvas-example

次に、App.vueコンポーネントの内容を次のコードに置き換えます。

src / App.vue
<template>
  <div id="app">
    <h2>Bar Chart Example</h2>
    <my-canvas style="width: 100%; height: 600px;">
      <my-box
        v-for="(obj, index) of chartValues"
        :key=index
        :x1="(index / chartValues.length) * 100"
        :x2="(index / chartValues.length) * 100 + 100 / chartValues.length"
        :y1="100"
        :y2="100 - obj.val"
        :color="obj.color"
        :value="obj.val"
      >
      </my-box>
    </my-canvas>
  </div>
</template>

<script>
import MyCanvas from './components/MyCanvas.vue';
import MyBox from './components/MyBox.vue';

export default {
  name: 'app',
  components: {
    MyCanvas,
    MyBox,
  },

  data() {
    return {
      chartValues: [
        { val: 24, color: 'red' },
        { val: 32, color: '#0f0' },
        { val: 66, color: 'rebeccapurple' },
        { val: 1, color: 'green' },
        { val: 28, color: 'blue' },
        { val: 60, color: 'rgba(150, 100, 0, 0.2)' },
      ],
    };
  },

  mounted() {
    let dir = 1;
    let selectedVal = Math.floor(Math.random() * this.chartValues.length);

    setInterval(() => {
      if (Math.random() > 0.995) dir *= -1;
      if (Math.random() > 0.99)
        selectedVal = Math.floor(Math.random() * this.chartValues.length);

      this.chartValues[selectedVal].val = Math.min(
        Math.max(this.chartValues[selectedVal].val + dir * 0.5, 0),
        100
      );
    }, 16);
  },
};
</script>

<style>
html,
body {
  margin: 0;
  padding: 0;
}

#app {
  position: relative;
  height: 100vh;
  width: 100vw;
  padding: 20px;
  box-sizing: border-box;
}
</style>

これはアプリテンプレートであり、setIntervalMath.random()を使用して16ミリ秒ごとにグラフの値を更新します。

MyCanvasMyBoxは2つのカスタムコンポーネントです。 my-boxの値は、キャンバスの幅のパーセンテージです。 各バーは、キャンバスの同じスペースを占めます。

ステップ2—Canvasコンポーネントを構築する

canvasコンポーネントは、canvas要素を作成し、リアクティブプロバイダーを介してすべての子コンポーネントにcanvasレンダリングコンテキストを挿入します。

src / components / MyCanvas.vue
<template>
  <div class="my-canvas-wrapper">
    <canvas ref="my-canvas"></canvas>
    <slot></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      provider: {
        context: null,
      },
    };
  },

  provide() {
    return {
      provider: this.provider,
    };
  },

  mounted() {
    this.provider.context = this.$refs['my-canvas'].getContext('2d');

    this.$refs['my-canvas'].width = this.$refs[
      'my-canvas'
    ].parentElement.clientWidth;
    this.$refs['my-canvas'].height = this.$refs[
      'my-canvas'
    ].parentElement.clientHeight;
  },
};
</script>

dataプロパティにproviderを作成すると、リアクティブになるため、contextが変更されたときに子コンポーネントが更新されます。 それが子供たちが描くCanvasRenderingContextになります。

provide()は、すべての子コンポーネントがinject: ['provider']にアクセスし、それにアクセスできるようにします。

キャンバスがDOMにマウントされるまで、レンダリングコンテキストにアクセスできません。 取得したら、すべての子コンポーネントに提供します。

次に、親の幅に合うようにキャンバスのサイズを変更します。 通常は、より柔軟なサイズ変更システムを使用します。

ステップ3—ボックスコンポーネントの構築

MyBox.vueは魔法が起こる場所です。 これは抽象コンポーネントであり、「実際の」コンポーネントではないため、実際にはDOMにレンダリングされません。

注:このコンポーネントにはテンプレートまたはスタイルがあります。

代わりに、render関数では、通常のキャンバス呼び出しを使用して、挿入されたキャンバスに描画します。 その結果、各コンポーネントは、追加の作業なしでプロパティが変更された場合でも再レンダリングされます。

components / MyBox.vue
<script>
const percentWidthToPix = (percent, ctx) =>
  Math.floor((ctx.canvas.width / 100) * percent);
const percentHeightToPix = (percent, ctx) =>
  Math.floor((ctx.canvas.height / 100) * percent);

export default {
  inject: ['provider'],

  props: {
    x1: {
      type: Number,
      default: 0,
    },
    y1: {
      type: Number,
      default: 0,
    },
    x2: {
      type: Number,
      default: 0,
    },
    y2: {
      type: Number,
      default: 0,
    },
    value: {
      type: Number,
      defualt: 0,
    },
    color: {
      type: String,
      default: '#F00',
    },
  },

  data() {
    return {
      oldBox: {
        x: null,
        y: null,
        w: null,
        h: null,
      },
    };
  },

  computed: {
    calculatedBox() {
      const ctx = this.provider.context;

      const calculated = {
        x: percentWidthToPix(this.x1, ctx),
        y: percentHeightToPix(this.y1, ctx),
        w: percentWidthToPix(this.x2 - this.x1, ctx),
        h: percentHeightToPix(this.y2 - this.y1, ctx),
      };

      // eslint-disable-next-line vue/no-side-effects-in-computed-properties
      this.oldBox = calculated;

      return calculated;
    },
  },

  // eslint-disable-next-line vue/require-render-return
  render() {
    if (!this.provider.context) return;

    const ctx = this.provider.context;
    const oldBox = this.oldBox;
    const newBox = this.calculatedBox;

    ctx.beginPath();
    ctx.clearRect(oldBox.x, oldBox.y, oldBox.w, oldBox.h);
    ctx.clearRect(newBox.x, newBox.y - 42, newBox.w, 100);

    ctx.rect(newBox.x, newBox.y, newBox.w, newBox.h);
    ctx.fillStyle = this.color;
    ctx.fill();

    ctx.fillStyle = '#000';
    ctx.font = '28px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText(
      Math.floor(this.value),
      newBox.x + newBox.w / 2,
      newBox.y - 14
    );
  },
};
</script>

percentWidthToPixpercentHeightToPixは、キャンバス領域のパーセンテージをピクセルに変換するヘルパー関数です。

inject: ['provider']は、親<my-canvas>コンポーネントからproviderプロパティを取得します。

親キャンバスを最初にマウントする必要があるため、render()関数が最初に実行されるまでにコンテキストが挿入されない可能性があります。 this.provider.contextが定義されているかどうかを確認してください。

oldBoxは、次のレンダリングでcalculatedBoxを再計算する前に領域をクリアできるように、前のレンダリングの寸法をキャッシュするために使用されます。

注:これは副作用をもたらしますが、チュートリアルのニーズに適しています。 ESLintエラーを回避するために、eslint-disable-next-lineを使用します。

変更を保存して、アプリケーションを実行します。

npm run serve

ブラウザでアプリケーションを開きます。

これは、Vueの反応性を使用してHTML5キャンバスに描画された棒グラフです。

結論

この記事では、HTML5キャンバスでVueを使用して基本的な棒グラフをレンダリングするための一連のコンポーネントを作成しました。

この方法は、あらゆる種類のキャンバスレンダリング、またはWebGLやWebVRを使用した3Dコンテンツにも使用できます。 想像力を使って!

チャレンジを続けて学習を続けましょう。注入されたプロバイダーに各ボックスのディメンションを渡して個々のイベント処理を追加し、イベントをディスパッチする場所を親キャンバスに決定させます。

モバイルバージョンを終了