Vue + Canvas で作るグラフィックアプリの骨格

# はじめに

この記事はもう一つの記事、
[Vue + SVG で作るグラフィックアプリの骨格](https://qiita.com/Satachito/items/071b7a398f0b7b17f663)
の Canvas 版です。

Canvas や SVG のおかげでブラウザ上でもインタラクティブにグラフィックを簡単に扱えるようになりました。

おまけに Vue を使うとまるでパソコン上のアプリを作るかのように Web 上にグラフィックアプリを作ることができます。

この記事では Vue + Canvas でマウスで四角を書いていくような 100 行程度の簡単なサンプルを作ることで、Web 上のグラフィックアプリの骨格を示したいと思います。

できあがったものは、以下のような感じです。
![ss.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73493/c546cf1e-9cd5-02b7-ff4d-26caa0fcc1b9.gif)

# 準備

* node, npm をダウンロードします

https://nodejs.org
この記事では node v12.4.0 で検証しています

* vue-cli をダウンロードして、とりあえず qvc という名前でアプリを作ります。

```sh
$ npm i @vue/cli -g
$ vue create qvc
$ cd qvc
$ yarn serve
```

この記事では vue 3.12.0 で検証しています。yarn を使ってない人は最後の行は

```
$ npm run serve
```

になると思います。

なお、以下のコードは ESLint の掟に従ってないので、`vue create qvc` の時に ESLint をはずさないと多分エラーになると思われます。
また、セパレータを前に書いたり、引数とか変数の名前が _ だったりするのは単なる趣味ですので、気に入らなくても怒らないでください。

# コード

src/App.vue を以下のようにしてみます。

``` App.vue
<template>
	<div id=app style="overflow: scroll">
		<div id=menu style="position: fixed; top: 0; left: 0">
			<button @click="mode='select'"	>select	</button>
			<button @click="mode='rect'"	>rect	</button>
		</div>
		<canvas
			style		= "margin: 24px 4px 4px 4px; background: aliceblue"
			:class		= "{ drawRect: mode == 'rect' }"
			:width		= "extent[ 0 ]"
			:height		= "extent[ 1 ]"
			@mousedown	= "mouseDown"
			@mousemove	= "mouseMove"
			@mouseup	= "mouseUp"
			@keyup.esc	= "keyUpESC"
		/>
	</div>
</template>

<script>
export default {
	data	: () => (
		{	b			: null
		,	c			: null
		,	mode		: 'select'
		,	elements	: []
		}
	)
,	computed: {
		extent	() {
			return [ 700, 500 ]
		}
	,	canvas	() {
			return this.$el.getElementsByTagName( 'canvas' )[ 0 ]
		}
	,	ctx		() {
			return this.canvas.getContext( '2d' )
		}
	,	dragRect() {
			return ( ! this.b || ! this.c )
			?	null
			:	[	this.b.offsetX
				,	this.b.offsetY
				,	this.c.offsetX - this.b.offsetX
				,	this.c.offsetY - this.b.offsetY
				]
		}
	}
,	methods	: {
		draw() {
			this.ctx.clearRect( 0, 0, ...this.extent )

			for ( let _ of this.elements ) this.ctx.strokeRect( ..._ )

			if ( ! this.dragRect ) return
			switch ( this.mode ) {
			case 'rect':
				this.ctx.setLineDash( [ 1 ] )
				this.ctx.strokeRect( ...this.dragRect )
				this.ctx.setLineDash( [] )
				break
			}
		}
	,	mouseDown( _ ) {
			this.b = _
			this.draw()
		}
	,	mouseMove( _ ) {
			this.c = _
			this.draw()
		}
	,	mouseUp( _ ) {
			this.c = _
			switch ( this.mode ) {
			case 'rect':
				this.elements.push( this.dragRect )
				break
			}
			this.b = null
			this.draw()
		}
	,	keyUpESC( _ ) {
			this.mode = 'select'
			this.draw()
		}
	}
,	mounted() {
		this.canvas.setAttribute( 'tabindex', 0 )
		this.draw()
	}
}
</script>

<style>
.drawRect {
	cursor: crosshair
}
</style>

```

# 勘所

## キーイベントの取得

canvas に tabindex 属性をつけると、キーイベントを取得できるようになります。

```
	this.canvas.setAttribute( 'tabindex', 0 )
```

## カーソルの切り替え

mode が 'select' と 'rect' の2つの状態が存在します。
'rect' の状態の時にクロスヘアカーソルを表示するために
[クラスとスタイルのバインディング](https://jp.vuejs.org/v2/guide/class-and-style.html)を動的に使っています。

```
		:class = "{ drawRect: mode == 'rect' }"
<style>
.drawRect {
	cursor: crosshair
}
</style>
```

# 最後に

この記事が何かの役にたてたら幸いです。わからないことがあればコメントください！