在 Vue 中创建签名板组件
大家好,今天我们将学习如何使用 Vue.js 创建一个签名板组件。
当您有特定需求时,创建自己的组件非常有用,它还可以让您了解组件背后的逻辑。
使用画布
我们将使用canvas HTML 标签,这样用户就可以在上面绘制签名。
<template>
<canvas />
</template>
增添一些风格:
<style scoped>
canvas {
border: 1px solid black;
background-color: white;
cursor: crosshair;
}
</style>
笔记:
- 这里我们使用 style 标签上的 scoped 属性,这样就可以将样式保留在组件内部。
- 我用十字准星定义光标(细节决定成败)。
是时候使用JavaScript了!
首先,我们将获取画布并向其传递一些参数。
data() {
return {
ctx : null,
}
},
mounted(){
this.ctx = this.$el.getContext('2d')
this.ctx.strokeStyle = 'black'
this.ctx.lineWidth = 2
}
笔记:
- strokeStyle 是签名的颜色。
- lineWidth 是签名的宽度。
让我们给画布添加 mousedown 事件,这样我们就能在用户点击画布时知道发生了什么。
<template>
<canvas @mousedown=”onMouseDown” />
</template>
data(){
return {
...
sign : false,
prevX : null,
prevY : null
}
}
methods: {
onMouseDown($event){
this.sign = true
this.prevX = $event.offsetX
this.prevY = $event.offsetY
}
}
...
- sign 属性可以用来判断用户是否点击了画布。
- prevX 和 prevY 属性允许通过从 $event 中检索来了解光标的当前位置。
我们换到二挡!
我们将为画布添加鼠标移动事件:
<template>
<canvas ... @mousemove="onMouseMove" />
</template>
methods: {
...
mousemove($event) {
if(this.sign) {
const currX = $event.offsetX
const currY = $event.offsetY
}
},
}
在这里,我们可以获取指针的当前位置,这将使我们能够绘制签名,这要归功于我们在 @onmousedown 事件中获取的先前位置。
画出签名
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()
}
}
评论:
- beginPath() 允许开始一个路径
- moveTo() 函数允许初始化起始点
- lineTo() 函数允许描述到达点
- closePath() 关闭路径
- stroke() 函数允许将路径应用到画布上。
现在,如果满足以下条件,我们将阻止用户在画布上绘制:
- 他的光标在画布外。
- 他的光标不再点击了
<template>
<canvas ... @mouseup="sign = false" @mouseout="sign = false" />
</template>
获取 v 模型并存储 canvas。
让我们定义 emit update 和 modelValue props。
emits : ['update:modelValue'],
props : {
modelValue : {
type : null,
required : true
}
},
让我们将画布上的图形转换为图像,并在绘制方法中更新 v 模型:
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)
}
}
最后一步!
现在我们需要检查组件的 v-model 是否为空,以便移除画布绘制的内容。
watch : {
modelValue(model) {
if(!model) {
this.ctx.clearRect(0, 0, this.$el.width, this.$el.height)
}
}
}
就是这样!
要在父视图中使用我们的组件,方法如下:
<template>
<MyCanvasComponent v-model="canvas" />
<button @click="canvas = null">Delete your signature</button>
</template>
import MyCanvasComponent from '@/components/MyCanvasComponents.vue
export default {
components : {
MyCanvasComponent
},
data(){
return {
canvas : null
}
}
}
整个组件代码:
<template>
<canvas @mousedown="mousedown" @mousemove="mousemove" @mouseup="sign = false" @mouseout="sign = false" />
</template>
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
}
}
<style scoped>
canvas {
border: 1px solid black;
background-color: white;
cursor: crosshair;
}
</style>