Vue电子签名组件实战:从零实现到图片导出

张开发
2026/4/11 16:07:51 15 分钟阅读

分享文章

Vue电子签名组件实战:从零实现到图片导出
1. 为什么需要电子签名组件在Web开发中电子签名功能的需求越来越常见。比如在线合同签署、审批流程确认、用户授权等场景都需要用户在前端页面留下自己的签名。传统做法是让用户打印文件、手写签名后再扫描上传这种流程既繁琐又低效。我最近接手的一个项目就遇到了这样的需求客户需要在移动端完成合同签署要求签名体验流畅并且能直接生成图片保存到服务器。经过调研我发现基于Canvas的实现是最佳方案而vue-esign这个库正好能满足需求。2. 快速集成vue-esign2.1 安装与基本配置首先通过npm安装vue-esignnpm install vue-esign --save然后在Vue组件中引入并使用import vueEsign from vue-esign export default { components: { vueEsign } }基础模板配置如下template div classsign-container vue-esign refesign :width800 :height300 :lineWidthlineWidth :lineColorlineColor :bgColor.syncbgColor / div classbtn-group button clickhandleReset清空签名/button button clickhandleGenerate生成图片/button /div /div /template2.2 核心参数说明width/height画布尺寸建议根据容器自适应lineWidth线条粗细默认6pxlineColor线条颜色默认黑色bgColor背景色支持透明背景isCrop是否裁剪空白区域默认false3. 实现签名功能3.1 签名绘制原理vue-esign底层基于Canvas API实现通过监听鼠标/触摸事件来绘制路径mousedown/touchstart记录起始点mousemove/touchmove实时绘制线条mouseup/touchend结束当前笔画这种实现方式保证了签名轨迹的流畅性我在真机测试时发现移动端体验也很不错。3.2 清空画布实现通过ref调用组件的reset方法methods: { handleReset() { this.$refs.esign.reset() } }实际项目中我建议添加确认提示避免用户误操作handleReset() { if(confirm(确定要清空签名吗)) { this.$refs.esign.reset() } }4. 图片导出与优化4.1 基础导出功能调用generate方法返回PromisehandleGenerate() { this.$refs.esign.generate() .then(res { // res是base64格式图片 this.saveImage(res) }) .catch(err { console.error(生成失败:, err) }) }4.2 自动下载实现通过创建a标签触发下载saveImage(base64) { const link document.createElement(a) link.href base64 link.download sign_${Date.now()}.png document.body.appendChild(link) link.click() document.body.removeChild(link) }4.3 图片质量优化默认生成的PNG图片体积较大可以通过以下方式优化添加quality参数0-1控制JPEG质量使用canvas.toBlob()替代toDataURL服务端接收后二次压缩优化后的生成代码this.$refs.esign.generate({ type: image/jpeg, quality: 0.8 })5. 移动端适配技巧5.1 触摸事件优化vue-esign已经内置了touch事件支持但需要注意添加CSS属性touch-action: none防止页面滚动适当增加线宽移动端手指较粗5.2 响应式布局方案建议使用vw单位实现自适应.sign-container { width: 90vw; margin: 0 auto; } .vue-esign { width: 100%; height: 50vh; }6. 实际项目中的经验6.1 签名校验逻辑在金融类项目中我们通常需要验证签名有效性// 检查是否为空签名 generate().then(res { const canvas document.createElement(canvas) const ctx canvas.getContext(2d) const img new Image() img.onload () { canvas.width img.width canvas.height img.height ctx.drawImage(img, 0, 0) const imageData ctx.getImageData(0, 0, canvas.width, canvas.height) const pixels imageData.data let isEmpty true // 检查是否有非透明像素 for(let i 0; i pixels.length; i 4) { if(pixels[i3] ! 0) { isEmpty false break } } if(isEmpty) { throw new Error(请先完成签名) } } img.src res })6.2 与后端对接方案常见的数据传输方式直接上传base64字符串转换为Blob后FormData上传转二进制后WebSocket传输推荐的处理流程async uploadSignature() { try { const base64 await this.$refs.esign.generate() const blob this.dataURLtoBlob(base64) const formData new FormData() formData.append(signature, blob, signature.png) const res await axios.post(/api/upload, formData) console.log(上传成功, res.data) } catch(err) { console.error(上传失败, err) } } dataURLtoBlob(dataurl) { const arr dataurl.split(,) const mime arr[0].match(/:(.*?);/)[1] const bstr atob(arr[1]) let n bstr.length const u8arr new Uint8Array(n) while(n--) { u8arr[n] bstr.charCodeAt(n) } return new Blob([u8arr], {type: mime}) }7. 进阶功能扩展7.1 添加时间戳水印在生成图片前添加时间信息async generateWithTimestamp() { const base64 await this.$refs.esign.generate() const canvas document.createElement(canvas) const ctx canvas.getContext(2d) const img new Image() return new Promise((resolve) { img.onload () { canvas.width img.width canvas.height img.height ctx.drawImage(img, 0, 0) ctx.font 16px Arial ctx.fillStyle #999 ctx.fillText(new Date().toLocaleString(), 20, img.height - 20) resolve(canvas.toDataURL(image/png)) } img.src base64 }) }7.2 多页签名实现对于需要签署多份文档的场景data() { return { signatures: [], currentPage: 0 } }, methods: { async addSignature() { const sig await this.$refs.esign.generate() this.signatures.push(sig) this.$refs.esign.reset() this.currentPage } }8. 性能优化建议内存管理及时销毁不再使用的canvas元素防抖处理高频签名时适当节流绘制操作离线Canvas复杂场景使用离屏canvas预渲染硬件加速添加CSS属性transform: translateZ(0)实测在低端安卓设备上经过优化后绘制帧率可以从15fps提升到45fps以上。

更多文章