
Vue.js + TypeScript だけで作るお手軽Webドット絵エディタ
-
2021年4月26日
こんにちは。みなさん今日もドット絵描いてますか?
ドット絵大好きなみなさんのために、Vue.js と TypeScript で作るお手軽Webドット絵エディターを作成しましたので、紹介させていただきます。
おしゃれなタイトルページもローディングも何もありません。ドット絵がすぐに描けます。
それでは使い方を順を追って説明していきます。
まず、vue-cli でサクッと vue2 + Babel + TS + EsLint + Prettier の環境を構築します。
sass が導入されていないので、node-sass, sass-loaderを追加します。
$ npm i -D node-sass sass-loader
実際の環境はこのようになっています。
ほぼ vue-cli で構築されたままです。
{ "name": "dot-editor", "version": "0.1.0", "private": true, "scripts": { "dev": "npm run serve", "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "core-js": "^3.6.5", "destyle.css": "^1.0.13", "vue": "^2.6.11", "vue-class-component": "^7.2.3", "vue-color": "^2.7.1", "vue-property-decorator": "^8.4.2" }, "devDependencies": { "@types/vue-color": "^2.4.2", "@typescript-eslint/eslint-plugin": "^3.2.0", "@typescript-eslint/parser": "^3.2.0", "@vue/cli-plugin-babel": "~4.4.0", "@vue/cli-plugin-eslint": "~4.4.0", "@vue/cli-plugin-typescript": "~4.4.0", "@vue/cli-service": "~4.4.0", "@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-typescript": "^5.0.2", "eslint": "^6.7.2", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-vue": "^6.2.2", "node-sass": "^4.14.1", "prettier": "^1.19.1", "sass-loader": "^8.0.2", "typescript": "~3.9.3", "vue-template-compiler": "^2.6.11" } }
①描画エリア
上記画像のハイライト部分が描画エリアです。
16 * 16 === 256 配列を作成し、プロパティに色情報を持たせています。
各配列内要素のイベントをトリガーした際に、指定している色に切り替えます。
background-color に色情報を bind しているので、色が変わります。
initDotList() { // 空の256ドット配列を作成 for (let i = 1; i <= DotLineCount; i++) { const dotLine = []; for (let i = 1; i <= DotLineCount; i++) { dotLine.push({ color: '' }); } this.dotList.push(dotLine); } }
export type DotList = [{ color: string }[]?];
:style="{ backgroundColor: dot.color }"
②プレビューエリア
プレビューエリアは canvas で作成しています。
各描画のイベントを trigger した際に、リアルタイムで canvas が render され、プレビューが表示されます。
drowCanvas() { let ctx: CanvasRenderingContext2D | null = this.canvas ? this.canvas.getContext('2d') : null; if (ctx !== null && this.canvas) ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); if (!this.dotList) return; this.dotList.forEach((list, listIndex) => { if (list) { list.forEach((val, valIndex) => { if (val.color && ctx !== null) { ctx.fillStyle = val.color; ctx.fillRect(valIndex * 32, listIndex * 32, 32, 32); } }); } }); }
③カラーピッカー
vue-colorというパッケージを利用しています。
とても便利で7種類のカラーピッカーがcomponentとして使用できます。
vue-color: https://www.npmjs.com/package/vue-color
$ npm i vue-color
このままだとコンパイルエラーが出るので型定義ファイルもインストールします。
$ npm i -D @types/vue-color
④カラーピッカーの切り替え
上記 vue-color で使用できる7種類のピッカーを dynamicComponent を使用して切り替えることができます。
切り替えが楽しくて入れたのですが、サイズがまばらでレイアウト対応できてないです、、、。
<PickerBtnBlock :picker-component="pickerComponent" @selectPicker="setPickerComponent" />
<component :is="pickerComponent" v-model="nowColor" />
⑤カラーヒストリーパレット
描画した際にヒストリー内に使用した色が登録されます。
17回分までヒストリーが残ります。
addHistoryColor(dot: { color: string }) { if (this.colorHistory.indexOf(dot.color) < 0) { this.colorHistory.push(dot.color); if (this.colorHistory.length > MaxHistoryCount) { this.colorHistory.shift(); } } }
⑥消しゴム
白と透明は別の扱いをしているので、消しゴムを選択しエディタで透明にします。
エディタ上で白と透明の判別ができていないのが課題です。
setDotColor(dot: { color: string }) { dot.color = this.isEraserMode ? '' : this.$refs.editorBlock.nowColor.hex; this.$refs.canvasBlock.drowCanvas(); if (!this.isEraserMode) this.addHistoryColor(dot); }
⑦その他機能
ただ純粋にドット絵を描きたいだけで作っていたのですが、作っているうちに楽しくなって色々機能をつけてみました。
- Downloadエディターとカラーヒストリーの状態をJSONファイルでダウンロードできます。
downloadJonFile(): void { const link: HTMLAnchorElement = document.createElement('a'); const data: { dotList?: DotList; colorHistory?: string[] } = { dotList: this.dotList, colorHistory: this.colorHistory, }; link.href = 'data:text/plain,' + encodeURIComponent(JSON.stringify(data)); // ファイル名は[UNIXTIME].json link.download = `${Math.round(new Date().getTime() / 1000)}.json`; link.click(); }
- Load JSONダウンロードしたJSONファイルを読み込み、エディターとカラーヒストリーに反映します。
loadJsonFile(event: Event): void { const target = event.target as HTMLInputElement; if (target.files) { const reader: FileReader = new FileReader(); reader.readAsText(target.files[0]); reader.addEventListener('load', (event: ProgressEvent) => { // value を消さないと同じファイルを連続で読み込む際に change event が発火しない if (this.inputFileElement) this.inputFileElement.value = ''; const target = event.target as FileAPIEventTarget; const result = JSON.parse(target.result); this.dotList = result.dotList; this.colorHistory = result.colorHistory; this.$nextTick().then(() => { this.$refs.canvasBlock.drowCanvas(); }); }); } }
- PNG Downloadプレビューエリアの状態をpngファイルでダウンロードできます。
downloadPngFile(): void { let link: HTMLAnchorElement = document.createElement('a'); link.href = this.canvas ? this.canvas.toDataURL('image/png') : ''; // ファイル名は取り合えずUNIXTIME link.download = `${Math.round(new Date().getTime() / 1000)}.png`; link.click(); }
- Demoダウンロードしたpngファイルをコマ送りアニメーションで表示できます。
startAnimation() { const leftBox = document.querySelector('.leftBox'); if (leftBox && this.pngFileList.length > 0) { const maxCount = this.pngFileList.length - 1; let i: number = 0; this.stepIntervalId = window.setInterval(() => { leftBox.innerHTML = '<img width="300" src="' + this.pngFileList[i] + '">'; i = i >= maxCount ? 0 : i + 1; }, 300); } }
以上、お手軽Webドット絵エディターのご紹介でした。
とりあえず手軽に作るということだけ考えて急いで作成したのですが、記事に載せることになったのでリファクタリングを行いました。
component は分割されてないし、ファイル、変数、関数名はわかりづらいし、型定義に any つかちゃってるし、eslint に怒られてるし、などなど盛りだくさんで、、。
反省とリファクタしてスッキリと両方の気持ちです。
それではみなさん良いドット絵ライフを。