Canvas应用之签名板
TIP
实现一个基于Canvas的具备重置、撤销能力的签名板
前置知识
绘制笔画
typescript
ctx.fillStyle = '#000';
ctx.beginPath(); // 开始绘制
ctx.arc(X, Y, 2, 0, 2 * Math.PI); // 绘制直径2 的圆点
ctx.fill(); // 填充实心
ctx.stroke(); // 渲染
事件
NOTE
注意在移动端下事件的监听可能有所不同
鼠标按下时注册移动事件,松开时释放事件监听
typescript
// 鼠标按下 注册事件
canvasRef!.value?.addEventListener('mousedown',()=>{
canvasRef!.value?.addEventListener('mousemove',PaintArc)
})
// 鼠标松开 卸载事件
canvasRef!.value?.addEventListener('mouseup',()=>{
canvasRef!.value?.removeEventListener('mousemove',PaintArc)
})
状态存储
将每次鼠标松开后的状态存储到队列中,一旦用户选择撤销则恢复上一次的状态即可.canvas
提供了画板的存储和恢复
typescript
const state= ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height) //将当前画板内容存储
ctx.putImageData(state, 0, 0) // 恢复画板到指定的状态
实现
vue
<script setup lang="ts">
import { onMounted,ref } from 'vue';
const canvasRef = ref<HTMLCanvasElement>()
const history = ref<any[]>([]); // 用于存储历史状态
// 绘制圆点
const PaintArc = (e:MouseEvent) => {
const ctx = canvasRef!.value?.getContext('2d') as CanvasRenderingContext2D
const offsetLeft = canvasRef!.value?.offsetLeft as number
const offsetTop = canvasRef!.value?.offsetTop as number
//按下坐标 - 起始点 = 实际偏移
const offsetX = Math.abs(offsetLeft - e.x)
const offsetY = Math.abs(offsetTop - e.y)
ctx.fillStyle = '#000';
ctx.beginPath(); // 开始绘制
ctx.arc(offsetX, offsetY, 2, 0, 2 * Math.PI); // 绘制直径2 的圆点
ctx.fill(); // 填充实心
ctx.stroke(); // 渲染
}
// 重置画板
const reset = () => {
const ctx = canvasRef!.value?.getContext('2d') as CanvasRenderingContext2D
const width = canvasRef!.value?.width as number
const height = canvasRef!.value?.height as number
// 填充画板为白底
ctx.fillStyle='#fff'
ctx.fillRect(0,0,width,height)
history.value = []
}
// 撤销
const cancel = () => {
const ctx = canvasRef!.value?.getContext('2d') as CanvasRenderingContext2D
if(history.value.length > 1){
history.value.pop(); // 移除最后一个状态
const lastState = history.value[history.value.length - 1];
ctx.putImageData(lastState, 0, 0); // 恢复到上一个状态
}else{
reset() // 只剩一个状态直接清空
}
}
onMounted(()=>{
// 鼠标按下 注册事件
canvasRef!.value?.addEventListener('mousedown',()=>{
canvasRef!.value?.addEventListener('mousemove',PaintArc)
})
// 鼠标松开 卸载事件
canvasRef!.value?.addEventListener('mouseup',()=>{
canvasRef!.value?.removeEventListener('mousemove',PaintArc)
const ctx = canvasRef!.value?.getContext('2d') as CanvasRenderingContext2D
const state = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height)
history.value.push(state)
})
})
</script>
<template>
<div class="wrap" >
<canvas ref="canvasRef" width="800" height="500" style="background-color:#fff" />
<div>
<button @click="cancel">撤销</button>
<button @click="reset">重置</button>
</div>
</div>
</template>
<style scoped>
.wrap{
display:flex;
flex-direction:column;
gap:10px;
justify-content:center;
align-items:center;
height:100%;
width:100%;
background-color:#ddd;
}
</style>