ぬにょす(挨拶)。
ブログのアイキャッチ画像に、記事タイトルを重ねたいなぁと思って。
でも、それだけのために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)
コメント