vue实现手写功能
实现手写功能的基本思路
在Vue中实现手写功能通常需要结合HTML5的Canvas API,通过监听鼠标或触摸事件来捕获用户的绘制路径,并将其渲染到画布上。核心步骤包括初始化画布、记录绘制路径、处理触摸事件兼容性等。
初始化Canvas画布
在Vue组件的模板中添加Canvas元素,并设置宽度和高度。建议通过ref获取DOM引用以便后续操作:
<template>
<canvas
ref="canvas"
@mousedown="startDrawing"
@mousemove="draw"
@mouseup="stopDrawing"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="stopDrawing"
></canvas>
</template>
在mounted生命周期中初始化画布上下文:
mounted() {
const canvas = this.$refs.canvas;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
this.ctx = canvas.getContext('2d');
this.ctx.lineWidth = 5;
this.ctx.lineCap = 'round';
this.ctx.strokeStyle = '#000000';
}
处理绘制逻辑
定义绘制状态变量和事件处理函数。使用isDrawing标记是否处于绘制状态,并记录路径点:

data() {
return {
isDrawing: false,
lastX: 0,
lastY: 0
};
},
methods: {
startDrawing(e) {
this.isDrawing = true;
const { offsetX, offsetY } = this.getPosition(e);
[this.lastX, this.lastY] = [offsetX, offsetY];
},
draw(e) {
if (!this.isDrawing) return;
const { offsetX, offsetY } = this.getPosition(e);
this.ctx.beginPath();
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(offsetX, offsetY);
this.ctx.stroke();
[this.lastX, this.lastY] = [offsetX, offsetY];
},
stopDrawing() {
this.isDrawing = false;
}
}
处理触摸事件兼容性
添加触摸事件的支持需要转换触摸坐标为画布坐标:
methods: {
getPosition(e) {
const canvas = this.$refs.canvas;
if (e.type.includes('touch')) {
const rect = canvas.getBoundingClientRect();
return {
offsetX: e.touches[0].clientX - rect.left,
offsetY: e.touches[0].clientY - rect.top
};
}
return { offsetX: e.offsetX, offsetY: e.offsetY };
},
handleTouchStart(e) {
e.preventDefault();
this.startDrawing(e);
},
handleTouchMove(e) {
e.preventDefault();
this.draw(e);
}
}
添加清除和保存功能
扩展功能按钮,实现清除画布和保存图像:

<button @click="clearCanvas">清除</button>
<button @click="saveAsImage">保存</button>
对应方法实现:
methods: {
clearCanvas() {
const canvas = this.$refs.canvas;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
},
saveAsImage() {
const canvas = this.$refs.canvas;
const link = document.createElement('a');
link.download = 'signature.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
}
优化绘制性能
对于频繁触发的mousemove事件,可以使用requestAnimationFrame进行节流:
draw(e) {
if (!this.isDrawing) return;
window.requestAnimationFrame(() => {
const { offsetX, offsetY } = this.getPosition(e);
this.ctx.beginPath();
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(offsetX, offsetY);
this.ctx.stroke();
[this.lastX, this.lastY] = [offsetX, offsetY];
});
}
完整组件示例
整合后的完整Vue组件代码:
<template>
<div>
<canvas
ref="canvas"
@mousedown="startDrawing"
@mousemove="draw"
@mouseup="stopDrawing"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="stopDrawing"
></canvas>
<div>
<button @click="clearCanvas">清除</button>
<button @click="saveAsImage">保存</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isDrawing: false,
lastX: 0,
lastY: 0,
ctx: null
};
},
mounted() {
const canvas = this.$refs.canvas;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
this.ctx = canvas.getContext('2d');
this.ctx.lineWidth = 5;
this.ctx.lineCap = 'round';
this.ctx.strokeStyle = '#000000';
},
methods: {
getPosition(e) {
const canvas = this.$refs.canvas;
if (e.type.includes('touch')) {
const rect = canvas.getBoundingClientRect();
return {
offsetX: e.touches[0].clientX - rect.left,
offsetY: e.touches[0].clientY - rect.top
};
}
return { offsetX: e.offsetX, offsetY: e.offsetY };
},
startDrawing(e) {
this.isDrawing = true;
const { offsetX, offsetY } = this.getPosition(e);
[this.lastX, this.lastY] = [offsetX, offsetY];
},
draw(e) {
if (!this.isDrawing) return;
window.requestAnimationFrame(() => {
const { offsetX, offsetY } = this.getPosition(e);
this.ctx.beginPath();
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(offsetX, offsetY);
this.ctx.stroke();
[this.lastX, this.lastY] = [offsetX, offsetY];
});
},
stopDrawing() {
this.isDrawing = false;
},
handleTouchStart(e) {
e.preventDefault();
this.startDrawing(e);
},
handleTouchMove(e) {
e.preventDefault();
this.draw(e);
},
clearCanvas() {
const canvas = this.$refs.canvas;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
},
saveAsImage() {
const canvas = this.$refs.canvas;
const link = document.createElement('a');
link.download = 'signature.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
}
};
</script>
<style scoped>
canvas {
border: 1px solid #000;
background-color: white;
touch-action: none;
}
</style>






