用vue实现手绘功能
使用 Vue 实现手绘功能
核心思路
基于 HTML5 的 Canvas API 结合 Vue 的响应式特性,通过监听鼠标或触摸事件实现手绘功能。需要处理画笔移动轨迹、颜色、粗细等属性,并提供清除画布功能。
基础实现代码
以下是一个完整的 Vue 3 组件示例(Composition API):

<template>
<div class="drawing-board">
<canvas
ref="canvasRef"
@mousedown="startDrawing"
@mousemove="draw"
@mouseup="stopDrawing"
@mouseleave="stopDrawing"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
<div class="controls">
<input type="color" v-model="drawColor">
<input type="range" v-model="lineWidth" min="1" max="50">
<button @click="clearCanvas">清除</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const canvasRef = ref(null)
const isDrawing = ref(false)
const drawColor = ref('#000000')
const lineWidth = ref(5)
let ctx = null
onMounted(() => {
const canvas = canvasRef.value
ctx = canvas.getContext('2d')
resizeCanvas()
window.addEventListener('resize', resizeCanvas)
})
function resizeCanvas() {
const canvas = canvasRef.value
canvas.width = canvas.offsetWidth
canvas.height = canvas.offsetHeight
ctx.lineJoin = 'round'
ctx.lineCap = 'round'
}
function startDrawing(e) {
isDrawing.value = true
draw(e)
}
function draw(e) {
if (!isDrawing.value) return
const rect = canvasRef.value.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
ctx.strokeStyle = drawColor.value
ctx.lineWidth = lineWidth.value
ctx.lineTo(x, y)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(x, y)
}
function stopDrawing() {
isDrawing.value = false
ctx.beginPath()
}
function handleTouchStart(e) {
e.preventDefault()
const touch = e.touches[0]
const mouseEvent = new MouseEvent('mousedown', {
clientX: touch.clientX,
clientY: touch.clientY
})
canvasRef.value.dispatchEvent(mouseEvent)
}
function handleTouchMove(e) {
e.preventDefault()
const touch = e.touches[0]
const mouseEvent = new MouseEvent('mousemove', {
clientX: touch.clientX,
clientY: touch.clientY
})
canvasRef.value.dispatchEvent(mouseEvent)
}
function handleTouchEnd() {
const mouseEvent = new MouseEvent('mouseup')
canvasRef.value.dispatchEvent(mouseEvent)
}
function clearCanvas() {
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
}
</script>
<style scoped>
.drawing-board {
width: 100%;
height: 500px;
position: relative;
}
canvas {
border: 1px solid #ccc;
background-color: white;
width: 100%;
height: 100%;
touch-action: none;
}
.controls {
margin-top: 10px;
display: flex;
gap: 10px;
align-items: center;
}
</style>
功能扩展建议
保存绘图结果

function saveAsImage() {
const dataURL = canvasRef.value.toDataURL('image/png')
const link = document.createElement('a')
link.download = 'drawing.png'
link.href = dataURL
link.click()
}
撤销/重做功能
const history = ref([])
const historyIndex = ref(-1)
function saveState() {
historyIndex.value++
history.value = history.value.slice(0, historyIndex.value)
history.value.push(canvasRef.value.toDataURL())
}
function undo() {
if (historyIndex.value <= 0) return
historyIndex.value--
const img = new Image()
img.onload = () => ctx.drawImage(img, 0, 0)
img.src = history.value[historyIndex.value]
}
function redo() {
if (historyIndex.value >= history.value.length - 1) return
historyIndex.value++
const img = new Image()
img.onload = () => ctx.drawImage(img, 0, 0)
img.src = history.value[historyIndex.value]
}
画笔类型扩展
const brushType = ref('pen')
function setBrush(type) {
brushType.value = type
switch(type) {
case 'marker':
ctx.globalCompositeOperation = 'multiply'
break
case 'eraser':
ctx.globalCompositeOperation = 'destination-out'
break
default:
ctx.globalCompositeOperation = 'source-over'
}
}
性能优化技巧
- 使用
requestAnimationFrame优化绘制性能 - 对于复杂绘图,考虑使用离屏 Canvas 进行缓冲
- 实现节流处理高频绘制事件
- 移动端适配时注意 passive event listeners
注意事项
- Canvas 尺寸必须通过属性设置(非 CSS),否则会出现拉伸变形
- 触摸事件需要正确处理 preventDefault 以避免页面滚动
- 高 DPI 设备需要处理像素比问题:
function resizeCanvas() { const canvas = canvasRef.value const ratio = window.devicePixelRatio || 1 canvas.width = canvas.offsetWidth * ratio canvas.height = canvas.offsetHeight * ratio ctx.scale(ratio, ratio) ctx.lineJoin = 'round' ctx.lineCap = 'round' }






