具体细节就不一一说了,这篇也是学习于:​​前端实现电子签名(web、移动端)通用​​

大家感兴趣可以去看看,是通过原生canvas实现,我这篇是对其进行了改造,并用在vue项目中,并且增加了浏览器改变画板可以达到一个自适应,签名完成之后,可以拿到图片的blob对象、以及base64编码等,根据需求适当选择,直接上代码吧:

我将 签名画板单独封装为一个组件,在需要的地方引入即可,注意要给外层容器一个宽高,该组件默认为宽高都为100%

<template>
<div style="width: 100%;height: 100%;">
<div class="drawing-board">
<canvas id="canvas"></canvas>
</div>
<div class="tool-bar">
<el-button @click="reset()" size="small" type="primary">清除</el-button>
<el-button @click="save()" size="small" type="success">确定</el-button>
</div>
</div>
</template>
<script>
const $ = name => document.querySelector(name)
const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent)) // 判断是否为移动端
// 配置内容
const config = {
width: 0, // 宽度
height: 0, // 高度
lineWidth: 5, // 线宽
strokeStyle: 'red', // 线条颜色
lineCap: 'round', // 设置线条两端圆角
lineJoin: 'round', // 线条交汇处圆角
}
// 偏移量
const client = {
offsetX: 0,
offsetY: 0
}
let canvas,ctx;
export default {
mounted(){
this.drawingBoardInit()
},
destroyed() {
window.removeEventListener(mobileStatus ? "touchstart" : "mousedown", this.init)
window.removeEventListener(mobileStatus ? "touchend" : "mouseup", this.cloaseDraw)
window.removeEventListener('resize', this.resetSize)
},
methods:{
drawingBoardInit() {
const { width, height, left, top } = $('.drawing-board').getBoundingClientRect()
config.width = width
config.height = height
client.offsetX = left
client.offsetY = top
// canvas 实例
canvas = $('#canvas')
// 设置宽高
canvas.width = config.width
canvas.height = config.height
// 设置边框
canvas.style.border = '1px solid #000'
// 创建上下文
ctx = canvas.getContext('2d')
// 设置填充背景色
ctx.fillStyle = 'transparent'
// 绘制填充矩形
ctx.fillRect(
0, // x 轴起始绘制位置
0, // y 轴起始绘制位置
config.width, // 宽度
config.height // 高度
);
// 创建鼠标/手势按下监听器
window.addEventListener(mobileStatus ? "touchstart" : "mousedown", this.init)
// 创建鼠标/手势 弹起/离开 监听器
window.addEventListener(mobileStatus ? "touchend" : "mouseup", this.cloaseDraw)
// 自适应画布
window.addEventListener('resize', this.resetSize)
},
// 鼠标按下
init(event) {
// 获取偏移量及坐标
const { clientX, clientY } = mobileStatus ? event.changedTouches[0] : event
// 清除以上一次 beginPath 之后的所有路径,进行绘制
ctx.beginPath()
// 根据配置文件设置相应配置
ctx.lineWidth = config.lineWidth
ctx.strokeStyle = config.strokeStyle
ctx.lineCap = config.lineCap
ctx.lineJoin = config.lineJoin
// 设置画线起始点位(减去 左边、上方的偏移量很关键)
ctx.moveTo(clientX - client.offsetX, clientY - client.offsetY)
// 监听 鼠标移动或手势移动
window.addEventListener(mobileStatus ? "touchmove" : "mousemove", this.draw)
},
// 绘制
draw(event) {
// 获取当前坐标点位
const { clientX, clientY } = mobileStatus ? event.changedTouches[0] : event
// 根据坐标点位移动添加线条(减去 左边、上方的偏移量很关键)
ctx.lineTo(clientX - client.offsetX, clientY - client.offsetY)
// 绘制
ctx.stroke()
},
// 结束绘制
cloaseDraw() {
// 结束绘制
ctx.closePath()
// 移除鼠标移动或手势移动监听器
window.removeEventListener("mousemove", this.draw)
},
// 清除
reset() {
// 清空当前画布上的所有绘制内容
ctx.clearRect(0, 0, config.width, config.height)
},
// 将画布内容保存为图片
save() {
// 将画布内容转为base64编码
let imgBase64 = canvas.toDataURL('png')
console.log(imgBase64, 'imgBase64-->>') // base64编码
// 将canvas上的内容转成blob流
canvas.toBlob(blob => {
console.log(blob, 'blob-->>') // 文件二进制流
// 获取当前时间,用来当做文件名
const date = new Date().getTime()
// 创建一个 a 标签
const link = document.createElement('a')
// 设置 a 标签的下载文件名
link.download = `${date}.png`
// 设置 a 标签的跳转路径为 文件流地址
link.href = URL.createObjectURL(blob)
// 手动触发 a 标签的点击事件
link.click()
// 移除 a 标签
link.remove()
})
},
// 浏览器分辨率改变 自适应重置
resetSize() {
const { width, height, left, top } = $('.drawing-board').getBoundingClientRect()
const dx = Math.abs(left - client.offsetX)
const dy = Math.abs(top - client.offsetY)
// 浏览器分辨率改变 重新设置配置项
config.width = width
config.height = height
client.offsetX = left
client.offsetY = top
// 重置canvas宽高 会导致 之前canvas的内容丢失,得先存一份,重置完宽高,再绘制上去
const canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height)
// 重置 canvas 宽高
canvas.width = width
canvas.height = height
// 改变完宽高 重绘画布
ctx.putImageData(canvasData, dx, dy)
}
}
}
</script>
<style scoped>
.drawing-board {
width: 100%;
height: calc(100% - 40px);
border-bottom: 1px solid #ccc;
box-sizing: border-box;
}
.tool-bar {
width: 100%;
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
}
</style>