Create Signature Pad Component in Vue
Tags: vue, javascript, webdev
Published: 2021-09-14 14:10:47


Hello, today we will see how to create a signature pad component using vuejs.

Creating your own components is very useful when you have specific needs, it also allows you to learn the logic behind the component.

## Use canvas

We are going to use a **canvas** HTML tag, this will allow the user to draw his signature.

```html
<template>
	<canvas />
</template>
```

## Add some style :

```html
<style scoped>
canvas {
	border: 1px solid black;
	background-color: white;
	cursor: crosshair;
}
</style>
```
Note: 
- Here we use the scoped property on the style tag which 
  allows to keep the style inside the component.
- I define the cursor by a crosshair (the details make the 
  differences).

## It's time to use javascript !
First of all we will get our canvas and pass it some parameters
```javascript
data() {
	return {
		ctx :  null,
	}
},
mounted(){
	this.ctx  = this.$el.getContext('2d')
	this.ctx.strokeStyle  =  'black'
	this.ctx.lineWidth  =  2
}
```
Notes:

 - strokeStyle is the color of the signature
 - lineWidth is the width of the signature

Let's add the mousedown event to our canvas which will let us know when the user clicks on our canvas.
```html
<template>
	<canvas @mousedown=”onMouseDown” />
</template>
```
```javascript
data(){
    return {
        ...
        sign  : false,
        prevX : null,
        prevY : null
    }
}
methods: {
	onMouseDown($event){
        this.sign = true
        this.prevX = $event.offsetX
        this.prevY = $event.offsetY
    }
}
...
```
- The sign property allows to know if the user has clicked on the canvas.
- The prevX and prevY properties allow to know the current position of the cursor by retrieving it from $event.

## We shift into second gear !
We will add the mousemove event to our canvas :

```html
<template>
	<canvas ... @mousemove="onMouseMove" />
</template>
```
```javascript
methods: {
	...
	mousemove($event) {
		if(this.sign) {
			const  currX  = $event.offsetX
			const  currY  = $event.offsetY
		}
	},
}
```

Here we get the current position of the pointer which will allow us to draw the signature thanks to the previous position we got in the @onmousedown event.

## Draw the signature
```javascript
methods: {
	...
	mousemove($event) {
		if(this.sign) {
			const  currX  = $event.offsetX
			const  currY  = $event.offsetY
			this.draw(this.prevX, this.prevY, currX, currY)
			this.prevX  =  currX
			this.prevY  =  currY
		}
	},
	draw(depX, depY, destX, destY){
		this.ctx.beginPath()
		this.ctx.moveTo(depX, depY)
		this.ctx.lineTo(destX, destY)
		this.ctx.closePath()
		this.ctx.stroke()
	}
}
```

Remarks:

 - beginPath() allows to start a path
 - moveTo() allows to initialize the starting point
 - lineTo() allows to describe the arrival point
 - closePath() closes the path
 - stroke() allows to apply the path to the canvas


Now we will prevent the user from drawing on the canvas if :

 - His cursor is outside the canvas
 - His cursor is not clicking anymore
 
```html
<template>
	<canvas ... @mouseup="sign = false" @mouseout="sign = false" />
</template>
```

## Get the v-model and store the canvas.
Let's define the emit update and the modelValue props
```javascript
emits : ['update:modelValue'],
props : {
	modelValue : {
		type :  null,
		required :  true
	}
},
```
Let's transform our canvas drawing into an image and update the v-model in our draw method:
```javascript
methods: {
	...
	draw(depX, depY, destX, destY) {
		this.ctx.beginPath()
		this.ctx.moveTo(depX, depY)
		this.ctx.lineTo(destX, destY)
		this.ctx.closePath()
		this.ctx.stroke()
		
		const img = this.$el.toDataURL('image/png').replace('image/png', 		'image/octet-stream')
		this.$emit('update:modelValue', img)
	}
}
```

## Last step !
Now we have to check if the v-model of our component is empty in order to remove our canvas drawing
```javascript
watch : {
	modelValue(model) {
		if(!model) {
			this.ctx.clearRect(0, 0, this.$el.width, this.$el.height)
		}
	}
}
```

## That's it!
To use our component in a parent view here is how to do it:
```html
<template>
	<MyCanvasComponent v-model="canvas" />
	<button @click="canvas = null">Delete your signature</button>
</template>
```
```javascript
import MyCanvasComponent from '@/components/MyCanvasComponents.vue
export default {
	components : {
		MyCanvasComponent
	},
	data(){
		return {
			canvas : null
		}
	}
}
```
## The entire component code :
```html
<template>
	<canvas @mousedown="mousedown" @mousemove="mousemove" @mouseup="sign = false" @mouseout="sign = false" />
</template>
```
```javascript
export  default {
	emits : ['update:modelValue'],
	props : {
		modelValue : {
			type :  null,
			required :  true
		}
	},
	data() {
		return {
			ctx :  null,
			sign :  false,
			prevX :  0,
			prevY :  0,
		}
	},
	methods : {
		mousedown($event) {
			this.sign  =  true
			this.prevX  = $event.offsetX
			this.prevY  = $event.offsetY
		},
		mousemove($event) {
			if(this.sign) {
				const  currX  = $event.offsetX
				const  currY  = $event.offsetY
				this.draw(this.prevX, this.prevY, currX, currY)
				this.prevX  =  currX
				this.prevY  =  currY
			}
		},
		draw(depX, depY, destX, destY) {
			this.ctx.beginPath()
			this.ctx.moveTo(depX, depY)
			this.ctx.lineTo(destX, destY)
			this.ctx.closePath()
			this.ctx.stroke()
			
			const img = this.$el.toDataURL('image/png').replace('image/png', 'image/octet-stream')
			this.$emit('update:modelValue', img)
		},
	},
	watch : {
		modelValue(model) {
			if(!model) {
			this.ctx.clearRect(0, 0, this.$el.width, this.$el.height)
			}
		}
	},
	mounted() {
		this.ctx  = this.$el.getContext('2d')
		this.ctx.strokeStyle  =  'black'
		this.ctx.lineWidth  =  2
	}
}
```
```css
<style scoped>
canvas {
    border: 1px solid black;
    background-color: white;
    cursor: crosshair;
}
</style>
```