Hey guys,
I developed an app that you can create and enjoy your own original coloring book from an image.
Look this!
You can also play coloring in this app.
The part about turning the image into a coloring book is almost the same as this article I wrote before.
So, in this article, I will explain how to develop this coloring functions using canvas.
DEMO→https://nurie-maker.com/
github→https://github.com/yuikoito/nurie-creator
※I'm using Nuxt.js here. Please replace it as necessary.
Draw a background image on the canvas
First, we need to prepare a canvas element and draw a background image on the canvas.
It takes a little time to draw the background image on the canvas, and I want to make sure to emit loading during that time.
Therefore, I wait here for the image to be fully loaded in Promise.
<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>
Now we can draw an image on canvas.
Use Retina Canvas Support
If you look at the image drawn above, you will see that the resolution is a little rough, so we will fix that part.
Specifically, when loading an image in canvas, load it at twice the size and set the canvas style to 1/2. The actual canvas drawing size will be the same as above, but the resolution will be finer and the image will be drawn beautifully.
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)
})
Implement a drawing function.
When a user clicks on the screen, the event can be detected by mousedown
on a PC, mousemove
for dragging, and mouseup
(or mouseout
) for the timing when the dragging is finished.
<canvas
ref="canvas"
@mousedown.prevent="dragStart"
@mouseup.prevent="dragEnd"
@mouseout.prevent="dragEnd"
@mousemove.prevent="draw"
>
</canvas>
※The lineWidth
and currentColor
can be changed by the user.
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
},
Change color of the line
I used vue-color to change color.
yarn add vue-color
Create vueColor.js
under plugins.
In this case, we are using a design called Sketch
, so we are only loading Sketch.
import Vue from 'vue'
import { Sketch } from 'vue-color'
Vue.component('Sketch', Sketch)
Add in nuxt.config.js.
plugins: [{ src: '@/plugins/vueColor.js', mode: 'client' }],
Then draw this component in the page.
The drawing is fine as it is, but you will get a warning, so move it inside <client-only>
.
<client-only>
<Sketch :value="colors" @input="updateValue"></Sketch>
</client-only>
Set a default value for colors.
When a user change color, you will get the color by the following function.
updateValue(e) {
this.currentColor = e.hex
},
This library is easy and useful!
Download the canvas
Then, we will implement a function to download the canvas when the button is clicked.
download() {
let link = document.createElement('a')
link.href = this.canvas.toDataURL('image/jpeg')
link.download = 'nurie-' + new Date().getTime() + '.jpg'
link.click()
},
... However, we can't download canvas at this point.
Execution of toDataURL fails due to CORS.
In fact, this time, for the background image of canvas, I was referring to the URL stored in s3 from uuid, so the error occurred due to CORS in that part.
image: `${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`
Since the image is loaded as shown above, the domain of the current URL and the domain of the image URL are different, causing the following error.
tainted canvases may not be exported
Then I used @nuxtjs/proxy to fix this problem.
yarn add @nuxtjs/proxy
Add nuxt.config.js
.
modules: ['@nuxtjs/dotenv', '@nuxtjs/proxy'],
proxy: {
'/nurie/': {
target: AWS_IMAGE_URL,
changeOrigin: true,
secure: false,
},
},
Now, you can replace ${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg
with the URL of your site.
Change the image part to the following.
image: `${process.env.BASE_URL}/nurie/${params.id}.jpg`,
Enable to play coloring on the phone
As it is, we can't do coloring on the phone, so we will also add events for the phone.
On the phone, you can detect events with touchstart
, touchmove
, and 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>
The events that can be detected on a smartphone differ from those on a PC only for the part where the line is drawn, so adjust the following.
The point is that on a PC you can get the location of the touch by e.clientX
, etc., but on a smartphone it is e.changedTouches[0].clientX
. (In the case of a single finger)
spDraw(e) {
if (e.changedTouches.length == 1) {
this.draw(e.changedTouches[0])
}
},
Finish!
That's it!
Thanks for reading.
I am very happy if you enjoy it!
🍎🍎🍎🍎🍎🍎
Please send me a message if you need.
yuiko.dev@gmail.com
https://twitter.com/yui_active
🍎🍎🍎🍎🍎🍎
Top comments (2)
Disegnidacolorarewk - The best collection of coloring pages for kids