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

Nuxt.js

ぬにょす(挨拶)。

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

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

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

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

動作デモ

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

Canvas Editor

使用コンポーネント

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

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

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

ハマりどころ

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

JavaScriptオブジェクトのwatch

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

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

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

キャンバスの遅延描画

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

updateCanvas() {
  if (this.updateTimer) {
    clearTimeout(this.updateTimer)
    this.updateTimer = null
  }

  this.updateTimer = setTimeout(() => {
    // 重たい描画処理
    this.updateTimer = null
  }, 500)
}

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)

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

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

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

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

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

画像として表示できているので、そのまま右クリックで保存できるかと思いきや、データが大きくなる(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()
}

コメント

タイトルとURLをコピーしました