HTML 5's Canvas API is an amazing tool for creating graphics on a page. From drawing basic shapes and animation to data visualization and on-the-fly video processing, Canvas API turns your dreams to reality using JavaScript. Let's learn how to integrate it with Vue.
🦄 What we're building 🦄
1. Create a canvas
Start off by creating a canvas in your DOM. Let's add a little CSS to keep the canvas' boundaries visible.
<template>
<div id="app">
<h1>Drawing with mousemove event</h1>
<canvas id="myCanvas" width="560" height="360" />
</div>
</template>
<style>
#myCanvas {
border: 1px solid grey;
}
</style>
Instantiate a Vue class and hook it up to you DOM.
new Vue({
el: '#app'
})
The trick to managing the canvas is by making it accessible to Vue by declaring a state and passing it the canvas' 2d context. We just need to make sure to do this after Vue and DOM have finished initialization. Enter mounted()
lifecycle hook.
new Vue({
el: '#app',
data: {
canvas: null,
},
mounted() {
var c = document.getElementById("myCanvas");
this.canvas = c.getContext('2d');
}
})
2. Reading mouse coordinates from mousemove
Using the mousemove
event, we can identify the exact location of the cursor in the screen. Create an event handler called showCoordinates
and pass it to the corresponding Vue directive.
The event handler will read the x- and y-coordinates from the MouseEvent interface. Use the properties offsetX
and offsetY
to take into consideration the (x,y) offset from the edge of the canvas. Make sure not to confuse these with clientX
and clientY
because they start from the top left corner of the visible screen.
<template>
<div id="app">
<span>{{x}}, {{y}}</span>
<h1>Drawing with mousemove event</h1>
<canvas id="myCanvas" width="560" height="360" @mousemove="showCoordinates"/>
</div>
</template>
new Vue({
// ...
data: {
canvas: null,
x: 0,
y: 0
},
methods: {
showCoordinates(e) {
this.x = e.offsetX;
this.y = e.offsetY;
}
},
// ...
})
3. Draw!
So far, so good. Knowing the exact coordinates of the cursor helps us determine where to draw on the canvas. Let's create a new function to draw a line and rename showCoordinates
to draw
. Inside draw
, call the function to draw a line.
new Vue({
// ...
methods: {
// ...
drawLine(x1, y1, x2, y2) {
let ctx = this.canvas;
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.closePath();
},
draw(e) {
this.drawLine(this.x, this.y, e.offsetX, e.offsetY);
this.x = e.offsetX;
this.y = e.offsetY;
}
},
// ...
})
Don't forget to update the Vue directive mousemove
to use draw
: <canvas id="myCanvas" width="560" height="360" @mousemove="draw"/>
Now we're getting somewhere! While your cursor is within the canvas boundaries, Vue keeps creating a line from old x- and y-coordinates to the next location.
But did you notice the ugly line from the top left corner? That's because we set the default (x,y) coordinates to be (0,0). We want to fix it but not by modifying the default coordinates. Instead, we need to tell Vue when to start and stop drawing. Just like how a pen shouldn't be able to transfer ink to paper just by hovering, the mouse shouldn't be able to draw just by moving over the canvas.
4. mousedown
, mouseup
Create a new state called isDrawing
and set the default value to false. Then define two methods to handle mousedown
and mouseup
events. Update draw
to use the isDrawing
flag. It looks like we added a lot of stuff but we're simply telling Vue to draw if and only if the left mouse button is pressed.
new Vue({
// ...
data: {
canvas: null,
x: 0,
y: 0,
isDrawing: false
},
methods: {
// ...
draw(e) {
if(this.isDrawing) {
this.drawLine(this.x, this.y, e.offsetX, e.offsetY);
this.x = e.offsetX;
this.y = e.offsetY;
}
},
beginDrawing(e) {
this.x = e.offsetX;
this.y = e.offsetY;
this.isDrawing = true;
},
stopDrawing(e) {
if (this.isDrawing) {
this.drawLine(this.x, this.y, e.offsetX, e.offsetY);
this.x = 0;
this.y = 0;
this.isDrawing = false;
}
}
},
// ...
})
Pass the new functions to the appropriate Vue directives: <canvas id="myCanvas" width="560" height="360" @mousemove="draw" @mousedown="beginDrawing" @mouseup="stopDrawing" />
. Then remove the coordinates from the dom to finish your project!
Top comments (1)
This is not the correct way to get elements from inside a component, this is the the correct way: