Vue.jsでHTML5キャンバスを制御する方法
序章
ほとんどの場合、DOMを介してWebページと対話するVue.jsコンポーネントを作成します。 しかし、Vueの反応性システムはそれ以上に役立ちます!
この記事では、HTML5キャンバスでVueを使用して基本的な棒グラフをレンダリングするための一連のコンポーネントを作成します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
このチュートリアルは、ノードv16.5.0、npm
v7.20.0、およびvue
v2.6.11で検証されました。
ステップ1—プロジェクトの設定
このプロジェクトは@vue /cliで開始できます。
- npx @vue/cli create vue-canvas-example --default
次に、新しいプロジェクトディレクトリに移動します。
- cd vue-canvas-example
次に、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>
これはアプリテンプレートであり、setInterval
とMath.random()
を使用して16ミリ秒ごとにグラフの値を更新します。
MyCanvas
とMyBox
は2つのカスタムコンポーネントです。 my-box
の値は、キャンバスの幅のパーセンテージです。 各バーは、キャンバスの同じスペースを占めます。
ステップ2—Canvasコンポーネントを構築する
canvasコンポーネントは、canvas要素を作成し、リアクティブプロバイダーを介してすべての子コンポーネントにcanvasレンダリングコンテキストを挿入します。
<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
関数では、通常のキャンバス呼び出しを使用して、挿入されたキャンバスに描画します。 その結果、各コンポーネントは、追加の作業なしでプロパティが変更された場合でも再レンダリングされます。
<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>
percentWidthToPix
とpercentHeightToPix
は、キャンバス領域のパーセンテージをピクセルに変換するヘルパー関数です。
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コンテンツにも使用できます。 想像力を使って!
チャレンジを続けて学習を続けましょう。注入されたプロバイダーに各ボックスのディメンションを渡して個々のイベント処理を追加し、イベントをディスパッチする場所を親キャンバスに決定させます。