Nuxt(Vue)でアイキャッチ画像を作るアプリを作ってみた

Nuxt.js

ぬにょす(挨拶)。

ブログのアイキャッチ画像に、記事タイトルを重ねたいなぁと思って。
でも、それだけのためにGIMP使うのもなぁ…ってゆーモノグサが発動し。
画像をピャッとやって文字をチャッと打って、はい完成、みたいなアプリを探してみたものの私の検索能力ではヒットせず。

無いものは作ればいいの精神で、Vue.js で HTML5 Canvas をアレコレして画像としてダウンロードできるWebアプリを作ったわけです。
もちろん、この記事のアイキャッチ画像はそいつで作成しました。

それがこちら → Canvas Editor (https://canvas-editor.amiiby.com/)

ソースは GitHub に置いてあります → リポジトリ

スポンサーリンク

動作デモ

実際に使ってる様子がこちら。

使用コンポーネント

カラーピッカーに vue-color コンポーネントを使わせてもらいました。

ドラッグ&ドロップによるレイヤーの並び替えに Vue.Draggable コンポーネントを使わせてもらいました。

UIコンポーネントは Buefy を使用しました。

ハマりどころ

実装する際に軽くハマったところを数点挙げておきます。

JavaScriptオブジェクトのwatch

レイヤーのデータ(座標とか色とか)をJavaScriptオブジェクトとして管理したのだけれど、Vue の watch にそのまま変数名を指定しただけでは、深い内部メンバの変更までは監視してくれないので、こんな書き方になりました。

watch: { // 注:layers は オブジェクトの配列 layers: { deep: true, handler: function(to, from){ this.updateCanvas() } },
Code language: JavaScript (javascript)

こちらの記事が大変参考になりました。

キャンバスの遅延描画

無事に如何なる変更も見逃さない監視体制を実装できたけれど、逆にデータ更新のたびにキャンバスの再描画が行われてしまいます。
数値にしろ文字列にしろ、変更途中の描画はキャンセルして、入力が終わったら描画するようにしたいと思いました。
これはタイマーの生成と破棄を行うことで実現できました。

updateCanvas() { if (this.updateTimer) { clearTimeout(this.updateTimer) this.updateTimer = null } this.updateTimer = setTimeout(() => { // 重たい描画処理 this.updateTimer = null }, 500) }
Code language: JavaScript (javascript)

500ミリ秒後に発動するタイマーの中で描画処理を行いますが、生成したタイマーのIDを保存しておき、updateCanvas が繰り返しコールされた場合にはタイマーを破棄してから再生成するというカラクリです。
これで不必要な描画処理をスキップすることができました。

Webフォントの追加ロード

文字のフォントに Google Fonts を使いたかったのですが、最初から全てのWebフォントCSS をロードするのは無駄くさいなぁと変な節約思考が働いたので、Webフォントが選択されたら該当するCSSの <link> タグを生成する方法を取りました。

const e = document.createElement('link') e.rel = 'stylesheet' e.href = href // 注:href には WebフォントCSS の URL が入ってます const head = document.getElementsByTagName('head')[0] head.appendChild(e)
Code language: JavaScript (javascript)

実際には複数のレイヤーで同じフォントを選択した時に同じ <link> タグを作らないようにしたり、フォントを変更した時に参照しなくなった <link> タグを削除したりといった処理も入れてます。

このアイデアはこちらの記事を参考にしました。

キャンバスのデータを画像ファイルにする

画面よりも大きなキャンバスを扱えるよう、<canvas> 自体は display: none; を付与して非表示にしています。
キャンバスのデータは toDataURL メソッドで得たデータを <img> タグの src 属性に渡すことで表示できます。

<!-- ビューテンプレート側 --> <img :src="canvasData">
Code language: HTML, XML (xml)
// スクリプト側 this.canvasData = canvas.toDataURL('image/png')
Code language: JavaScript (javascript)

画像として表示できているので、そのまま右クリックで保存できるかと思いきや、データが大きくなる(2MB以上?)とエラーになってしまいました。

こちらの記事が解決に導いてくれました。

ダウンロードボタンを用意して、クリックされたらキャンバスデータをBlobに変換して送出することで上手くいきました。

onClickDownload() { if (this.blobUrl) { URL.revokeObjectURL(this.blobUrl) } this.blobUrl = this.Base64toBlob(this.canvasData) const url = URL.createObjectURL(this.blobUrl) const a = document.createElement('a') a.download = 'CanvasData.png' a.href = url a.click() }
Code language: JavaScript (javascript)

コメント