今週はずっと前に作った塗り絵ツクールのアップデートということで、塗り絵機能をつけました。
塗り絵ツクールで公開されてる塗り絵やシェアされたURLから作成した塗り絵画像に色をつけることができるようにしました!(スマホ対応してますが、現在はPC推奨です)#塗り絵ツクールhttps://t.co/bKEDtJe3Ih pic.twitter.com/LzDjbayrli
— Yui 🌕 Yuiko Ito (@yui_active) August 23, 2021
今回はこの塗り絵機能に関して書きます。
参考になったと思えばLGTM してもらえれば励みになります。
canvasに背景画像をつける
まずはcanvasエレメントを用意して、canvasに背景画像を描画します。
canvasに背景画像を描画する際に少し時間がかかるので、その間はローディングを出すようにしています。
そのため、Promiseで画像が完全に読み込まれるのを待ってからoverlayフラグをfalseに戻します。
<script>
export default {
async asyncData({ params }) {
return {
url: `${process.env.BASE_URL}/nurie/${params.id}`,
image: `${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`,
twitterImage: `${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`,
}
},
data() {
return {
canvas: null,
ctx: null,
noPicture: require('@/assets/img/noPic.png'),
overlay: true,
}
},
mounted() {
this.init()
},
methods: {
async init() {
this.canvas = this.$refs.canvas
this.ctx = this.canvas.getContext('2d')
const wrapper = this.$refs.wrapper
await this.loadImage(this.image).then((res) => {
const scale = wrapper.clientWidth / res.naturalWidth
this.canvas.width = res.naturalWidth * scale
this.canvas.height = res.naturalHeight * scale
this.ctx.drawImage(res, 0, 0, this.canvas.width, this.canvas.height)
})
this.overlay = false
},
loadImage(src) {
return new Promise((resolve) => {
const img = new Image()
img.src = src
img.onload = () => resolve(img)
img.onerror = () => {
img.src = this.noPicture
}
})
},
}
}
</script>
これでcanvasに画像が描画できました。
canvasで描画後の画像が荒くなる
上記で描画された画像を見ると少し解像度が荒くなってしまいましたので、その部分を修正します。
具体的にはcanvasで画像を読み込む際に2倍の大きさで読み込んでおき、canvasのstyleを1/2にすることで、実際のcanvasの描画サイズは上記と同じなのですが、解像度が細かくなりきれいに描画できます。
await this.loadImage(this.image).then((res) => {
this.ctx.scale(2, 2)
const scale = wrapper.clientWidth / res.naturalWidth
this.canvas.width = res.naturalWidth * scale * 2
this.canvas.height = res.naturalHeight * scale * 2
this.canvas.style.width = this.canvas.width / 2 + 'px'
this.canvas.style.height = this.canvas.height / 2 + 'px'
this.ctx.drawImage(res, 0, 0, this.canvas.width, this.canvas.height)
})
お絵かき機能を実装する
ユーザーがクリックする際の操作はPCだとmousedown
、ドラッグはmousemove
、ドラッグが終わったタイミングはmouseup
(もしくはmouseout
)でイベントが探知できるので、それを利用します。
<canvas
ref="canvas"
@mousedown.prevent="dragStart"
@mouseup.prevent="dragEnd"
@mouseout.prevent="dragEnd"
@mousemove.prevent="draw"
>
</canvas>
※lineWidth
とcurrentColor
はユーザーの操作によって変動できるようにしています。
dragStart() {
this.ctx.beginPath()
this.isDrag = true
},
draw(e) {
const x = (e.clientX - this.canvas.getBoundingClientRect().left) * 2
const y = (e.clientY - this.canvas.getBoundingClientRect().top) * 2
if (!this.isDrag) {
return
}
this.ctx.lineCap = 'round'
this.ctx.lineJoin = 'round'
this.ctx.lineWidth = this.lineWidth
this.ctx.strokeStyle = this.currentColor
if (this.lastPosition.x === null || this.lastPosition.y === null) {
this.ctx.moveTo(x, y)
} else {
this.ctx.moveTo(this.lastPosition.x, this.lastPosition.y)
}
this.ctx.lineTo(x, y)
this.ctx.stroke()
this.lastPosition.x = x
this.lastPosition.y = y
},
dragEnd() {
this.ctx.closePath()
this.isDrag = false
this.lastPosition.x = null
this.lastPosition.y = null
this.isErase = false
},
線の色を変える
線の色を変える部分はvue-colorというライブラリを利用しました。
yarn add vue-color
plugins配下にvueColor.jsを作ってvue-colorの読み込み部分を書きます。
今回はSketchというデザインを使うのでSketchだけを読み込んでます。
import Vue from 'vue'
import { Sketch } from 'vue-color'
Vue.component('Sketch', Sketch)
nuxt.config.jsに以下追加
plugins: [{ src: '@/plugins/vueColor.js', mode: 'client' }],
後はこれを該当のページ部分で描画するだけです。
このままでも描画は問題ないのですが、warningが出てしまうので、<client-only>
の中で動かします。
<client-only>
<Sketch :value="colors" @input="updateValue"></Sketch>
</client-only>
colorsには初期値を設定しておきます。hexでもrgbaでもどちらでも設定できます。
色が変更されたら以下の関数で色を取得できます。
updateValue(e) {
this.currentColor = e.hex
},
めちゃくちゃ使い勝手がよく、楽ちんでした
塗り絵をしたcanvasをダウンロードする
後はボタンをクリックするとcanvasをダウンロードする機能を実装します。
適当にボタンを用意して、以下の関数でcanvasをダウンロードします。
download() {
let link = document.createElement('a')
link.href = this.canvas.toDataURL('image/jpeg')
link.download = 'nurie-' + new Date().getTime() + '.jpg'
link.click()
},
...が、このままではcanvasがダウンロードできません。
CORSが原因でtoDataURLの実行に失敗する
実は今回、canvasの背景画像に関してはuuidからs3に保存してあるURLを参照するようにしていたので、その部分のCORSが原因でエラーが発生してしまいました。
image: `${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`
imageを↑のように読み込んでいるので、現在のURLのドメインと画像のURLのドメインが異なるため、以下のエラーが発生。
tainted canvases may not be exported
汚染されてるcanvas...oh...
ということで、今回CORS対策を@nuxtjs/proxyで解決しました。
※当初AWSでCORS対策をしてたのですが、なんでか画像によってうまくいったりいかなかったりという事が起きたのでAWSでのCORS設定に加えて、@nuxtjs/proxyでも設定するようにしました。
yarn add @nuxtjs/proxy
nuxt.config.jsに追記。
modules: ['@nuxtjs/dotenv', '@nuxtjs/proxy'],
proxy: {
'/nurie/': {
target: AWS_IMAGE_URL,
changeOrigin: true,
secure: false,
},
},
これで${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg
と書いていた部分はサイトのURLを使って置き換えることができるようになりました。
image部分を以下に変更します。
image: `${process.env.BASE_URL}/nurie/${params.id}.jpg`,
スマホで塗り絵をできるようにする
このままではスマホで塗り絵ができないので、スマホのイベントも追加します。
スマホではtouchstart
, touchmove
, touchend
でイベントを探知できます。
<canvas
ref="canvas"
@mousedown.prevent="dragStart"
@touchstart.prevent="dragStart"
@touchend.prevent="dragEnd"
@mouseup.prevent="dragEnd"
@mouseout.prevent="dragEnd"
@mousemove.prevent="draw"
@touchmove.prevent="spDraw"
>
</canvas>
線を書く部分だけスマホだとPCと探知できるイベントが異なるので、以下で調整します。
要はPCだとタッチしてる場所などはe.clientX
等で取得できますが、スマホだとe.changedTouches[0].clientX
になるのでその対策です。(一本指の場合)
spDraw(e) {
if (e.changedTouches.length == 1) {
this.draw(e.changedTouches[0])
}
},
今回は一本指にしか対応してませんが、もし複数指に対応させる場合はforでchangedTouchesを回して、this.draw(e.changedTouches[i])
にでもすればOKだと思います。
JavaScript タッチイベントの取得(マルチ対応) サンプルコードの記事を非常に参考にさせて頂きました。
これで塗り絵機能ができました!
あとがき
これで14週目の週イチ発信となりました。
良ければこれまでの週イチ発信も見て下さい!
ではでは〜。
- 【React + Typescriptで顔認識】tensorflowを使って画像にマスクをかけるアプリを作った
- 【React + Typescript】ボタン一つでコンポーネントのscssをコピーできるサイトを作った
- 【アップデート】ui-componentsに18個のコンポーネントを追加した
- 【Nuxt.js × Tailwind CSS】ボタン一つで有名絵画風の画像にできるサービスをリリースした!
- 【GASでLINE Bot作成】現在地の近くのおすすめのごはん屋さんを教えてくれるLINE Botを作った
- 【動的OGP】Next.js + TypeScript + Vercelデプロイで動的OGPを実現する
- 【LambdaでOpenCVを利用】AWSとOpenCVを利用してポケモン画像でアスキーアート風に変換するAPIを作った
- ポケモン画像でアスキーアート風に変換するwebアプリを作った
- ボタン一つで漫画風の画像にできるサービスを作った
- 【Next.js + TensorFlowでweb cameraにバーチャル背景をつける】バーチャル旅行を体験できるアプリを作った
- オリンピックを盛り上げるためにピクトグラムさんになれるアプリを作った【Next.js+TypeScriptでTensorFlow.jsを使って姿勢検出】
- 毎週アプリをリリースして3ヶ月経ったので振り返る