在现代Web应用中,电子签名功能变得越来越常见。无论是合同签署、表单提交还是其他需要用户确认的场景,电子签名都提供了一种便捷的方式来获取用户的确认。本文将详细介绍如何使用Vue.js和HTML5的Canvas技术来实现一个简单的电子签名功能。
Vue.js 是一个用于构建用户界面的渐进式JavaScript框架。它的核心库专注于视图层,易于与其他库或现有项目集成。Vue.js 的设计目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件。
HTML5 的 <canvas>
元素提供了一个用于绘制图形的API。通过JavaScript,我们可以在Canvas上绘制各种图形、文本、图像等。Canvas 非常适合用于实现绘图、动画、游戏等需要动态图形渲染的场景。
在开始之前,我们需要创建一个新的Vue项目。如果你还没有安装Vue CLI,可以通过以下命令进行安装:
npm install -g @vue/cli
然后,创建一个新的Vue项目:
vue create vue-canvas-signature
在项目创建过程中,你可以选择默认配置或手动选择需要的特性。创建完成后,进入项目目录并启动开发服务器:
cd vue-canvas-signature npm run serve
在 src/components
目录下创建一个新的组件文件 SignaturePad.vue
。这个组件将包含我们的电子签名功能。
<template> <div class="signature-pad"> <canvas ref="canvas" @mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas> <div class="controls"> <button @click="clearCanvas">清除</button> <button @click="saveSignature">保存</button> </div> </div> </template> <script> export default { name: 'SignaturePad', data() { return { isDrawing: false, lastX: 0, lastY: 0, }; }, methods: { startDrawing(event) { this.isDrawing = true; const canvas = this.$refs.canvas; const rect = canvas.getBoundingClientRect(); this.lastX = event.clientX - rect.left; this.lastY = event.clientY - rect.top; }, draw(event) { if (!this.isDrawing) return; const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; ctx.beginPath(); ctx.moveTo(this.lastX, this.lastY); ctx.lineTo(x, y); ctx.stroke(); this.lastX = x; this.lastY = y; }, stopDrawing() { this.isDrawing = false; }, clearCanvas() { const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); }, saveSignature() { const canvas = this.$refs.canvas; const image = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.href = image; link.download = 'signature.png'; link.click(); }, }, mounted() { const canvas = this.$refs.canvas; canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; const ctx = canvas.getContext('2d'); ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.lineCap = 'round'; }, }; </script> <style scoped> .signature-pad { display: flex; flex-direction: column; align-items: center; } canvas { border: 1px solid #000; background-color: #fff; width: 100%; height: 300px; } .controls { margin-top: 10px; } button { margin: 0 5px; padding: 5px 10px; cursor: pointer; } </style>
在 mounted
钩子中,我们初始化了Canvas的宽度和高度,并设置了绘图上下文的一些默认属性,如线条颜色、宽度和线条端点的样式。
mounted() { const canvas = this.$refs.canvas; canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; const ctx = canvas.getContext('2d'); ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.lineCap = 'round'; }
我们监听了Canvas的 mousedown
、mousemove
、mouseup
和 mouseleave
事件,以便在用户开始绘制、移动鼠标、停止绘制或离开Canvas时执行相应的操作。
startDrawing(event) { this.isDrawing = true; const canvas = this.$refs.canvas; const rect = canvas.getBoundingClientRect(); this.lastX = event.clientX - rect.left; this.lastY = event.clientY - rect.top; }, draw(event) { if (!this.isDrawing) return; const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; ctx.beginPath(); ctx.moveTo(this.lastX, this.lastY); ctx.lineTo(x, y); ctx.stroke(); this.lastX = x; this.lastY = y; }, stopDrawing() { this.isDrawing = false; },
在 draw
方法中,我们使用Canvas的绘图API来绘制线条。通过 beginPath
、moveTo
和 lineTo
方法,我们可以在Canvas上绘制出用户鼠标移动的路径。
clearCanvas
方法使用 clearRect
方法清除Canvas上的所有内容,从而实现清除签名的功能。
clearCanvas() { const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); },
saveSignature
方法将Canvas上的内容转换为图像,并通过创建一个 <a>
元素来触发下载。
saveSignature() { const canvas = this.$refs.canvas; const image = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.href = image; link.download = 'signature.png'; link.click(); },
为了支持触摸设备,我们需要添加对 touchstart
、touchmove
和 touchend
事件的监听。
<template> <div class="signature-pad"> <canvas ref="canvas" @mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing" @touchstart="startDrawingTouch" @touchmove="drawTouch" @touchend="stopDrawing"></canvas> <div class="controls"> <button @click="clearCanvas">清除</button> <button @click="saveSignature">保存</button> </div> </div> </template> <script> export default { name: 'SignaturePad', data() { return { isDrawing: false, lastX: 0, lastY: 0, }; }, methods: { startDrawing(event) { this.isDrawing = true; const canvas = this.$refs.canvas; const rect = canvas.getBoundingClientRect(); this.lastX = event.clientX - rect.left; this.lastY = event.clientY - rect.top; }, startDrawingTouch(event) { this.isDrawing = true; const canvas = this.$refs.canvas; const rect = canvas.getBoundingClientRect(); const touch = event.touches[0]; this.lastX = touch.clientX - rect.left; this.lastY = touch.clientY - rect.top; }, draw(event) { if (!this.isDrawing) return; const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; ctx.beginPath(); ctx.moveTo(this.lastX, this.lastY); ctx.lineTo(x, y); ctx.stroke(); this.lastX = x; this.lastY = y; }, drawTouch(event) { if (!this.isDrawing) return; const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); const touch = event.touches[0]; const x = touch.clientX - rect.left; const y = touch.clientY - rect.top; ctx.beginPath(); ctx.moveTo(this.lastX, this.lastY); ctx.lineTo(x, y); ctx.stroke(); this.lastX = x; this.lastY = y; }, stopDrawing() { this.isDrawing = false; }, clearCanvas() { const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); }, saveSignature() { const canvas = this.$refs.canvas; const image = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.href = image; link.download = 'signature.png'; link.click(); }, }, mounted() { const canvas = this.$refs.canvas; canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; const ctx = canvas.getContext('2d'); ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.lineCap = 'round'; }, }; </script>
为了实现撤销功能,我们可以使用一个数组来存储每一步的绘图操作。每次绘制时,我们将当前Canvas的状态保存到数组中。当用户点击撤销按钮时,我们可以从数组中恢复上一个状态。
data() { return { isDrawing: false, lastX: 0, lastY: 0, history: [], }; }, methods: { startDrawing(event) { this.isDrawing = true; const canvas = this.$refs.canvas; const rect = canvas.getBoundingClientRect(); this.lastX = event.clientX - rect.left; this.lastY = event.clientY - rect.top; this.saveState(); }, draw(event) { if (!this.isDrawing) return; const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; ctx.beginPath(); ctx.moveTo(this.lastX, this.lastY); ctx.lineTo(x, y); ctx.stroke(); this.lastX = x; this.lastY = y; }, stopDrawing() { this.isDrawing = false; this.saveState(); }, saveState() { const canvas = this.$refs.canvas; const image = canvas.toDataURL(); this.history.push(image); }, undo() { if (this.history.length > 1) { this.history.pop(); const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const image = new Image(); image.src = this.history[this.history.length - 1]; image.onload = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(image, 0, 0); }; } }, clearCanvas() { const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); this.history = []; this.saveState(); }, saveSignature() { const canvas = this.$refs.canvas; const image = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.href = image; link.download = 'signature.png'; link.click(); }, },
重做功能与撤销功能类似,我们可以使用另一个数组来存储被撤销的操作。当用户点击重做按钮时,我们可以从该数组中恢复被撤销的操作。
data() { return { isDrawing: false, lastX: 0, lastY: 0, history: [], redoHistory: [], }; }, methods: { startDrawing(event) { this.isDrawing = true; const canvas = this.$refs.canvas; const rect = canvas.getBoundingClientRect(); this.lastX = event.clientX - rect.left; this.lastY = event.clientY - rect.top; this.saveState(); }, draw(event) { if (!this.isDrawing) return; const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; ctx.beginPath(); ctx.moveTo(this.lastX, this.lastY); ctx.lineTo(x, y); ctx.stroke(); this.lastX = x; this.lastY = y; }, stopDrawing() { this.isDrawing = false; this.saveState(); }, saveState() { const canvas = this.$refs.canvas; const image = canvas.toDataURL(); this.history.push(image); this.redoHistory = []; }, undo() { if (this.history.length > 1) { const lastState = this.history.pop(); this.redoHistory.push(lastState); const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const image = new Image(); image.src = this.history[this.history.length - 1]; image.onload = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(image, 0, 0); }; } }, redo() { if (this.redoHistory.length > 0) { const nextState = this.redoHistory.pop(); this.history.push(nextState); const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const image = new Image(); image.src = nextState; image.onload = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(image, 0, 0); }; } }, clearCanvas() { const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); this.history = []; this.redoHistory = []; this.saveState(); }, saveSignature() { const canvas = this.$refs.canvas; const image = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.href = image; link.download = 'signature.png'; link.click(); }, },
在某些场景下,我们可能需要对用户的签名进行验证,以确保签名的有效性。我们可以通过检查Canvas上是否有绘制内容来实现简单的签名验证。
methods: { isSignatureValid() { const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { if (data[i + 3] !== 0) { return true; } } return false; }, saveSignature() { if (!this.isSignatureValid()) { alert('请先签名'); return; } const canvas = this.$refs.canvas; const image = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.href = image; link.download = 'signature.png'; link.click(); }, },
通过本文的介绍,我们学习了如何使用Vue.js和HTML5的Canvas技术来实现一个简单的电子签名功能。我们实现了基本的绘制、清除、保存功能,并进一步优化
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。