DEV Community

Rei Allen Ramos
Rei Allen Ramos

Posted on

How to use HTML Canvas with Vue

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 🦄

4_mousedown_mouseup

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'
})

1_setup_canvas

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;
    }
  },
  // ...
})

2_reading_coordinates

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"/>

3_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!

Clik here for the complete code.

Top comments (1)

Collapse
 
seedyrom profile image
Zack Kollar • Edited

This is not the correct way to get elements from inside a component, this is the the correct way:

<template>
     <div ref="referenceElement">
     </div>
</template>
<script>
export default {
    name: "FakeComponent",
    mounted() {
        this.$refs["referenceElement"] // This is the correct way to get the internal dom elems!
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode