在 Vue 3 中实现电子签名组件

devtools/2024/10/23 3:18:43/

在 Vue 3 中实现一个简单的电子签名组件,并解决一个常见问题:当签名组件放在弹窗内时,鼠标绘制会出现偏移的问题。

项目环境:
  • Vue 3前端框架
  • Element Plus:UI 组件库

电子签名组件功能

  1. 画布绘制:用户可以在画布上使用鼠标进行签名。
  2. 画笔设置:支持调整画笔的颜色和大小。
  3. 提交签名:将画布内容提交,生成图片并回传给父组件。
  4. 清除签名:可以清除当前画布内容。

源代码实现

下面是完整的 Vue 3 电子签名组件代码:

1. template 模板部分
<template><!-- 电子签名组件 --><div><!-- 画布对象 --><divref="grapDiv"style="width: 800px; height: 400px; border: 2px dotted #ddd; border-radius: 10px;"v-show="!isCommit"><canvasref="grapCvs"id="container"@mousedown="mousedown"@mousemove="mousemove"/></div><!-- 设置面板 --><divref="setControlDiv"style="width: 280px; height: 200px; background-color: #474747; border: 1px solid #ddd; border-radius: 10px; margin-top: -203px; margin-left: 1px; position: absolute;"v-show="ifSetController"><div style="width: 100%; height: 30%; margin-top: 10px"><span style="width: 100%; height: 30%"><label style="float: left; color: white; margin-left: 14px; margin-top: 15px;">字体大小</label><label style="float: right; color: white; margin-right: 14px; margin-top: 15px;">{{ fontSize }}</label></span><el-slider v-model="fontSize" style="width: 89%; margin-top: 20px; margin-left: 17px" :min="1" :max="10" /></div><div style="width: 100%; height: 45%; margin-top: 15px; padding: 7px"><li@click="setImgColor"v-for="(item, index) in fontColorArray":key="index":style="'list-style: none;width: 38px;height: 38px;float: left;background: ' + item"></li></div></div><!-- 提交的画布对象 --><div style="width: 800px; height: 400px; border: 2px solid green; border-radius: 10px;" v-show="isCommit"><img :src="content" /></div><!-- 操作按钮 --><div style="width: 800px; padding-top: 10px"><el-button @click="set()" v-show="!isCommit">画笔设置</el-button><el-button @click="clear()" v-show="!isCommit">清除</el-button><el-button type="primary" @click="commit()" v-show="!isCommit">提交</el-button><el-button @click="goback()" v-show="isCommit">返回</el-button></div></div>
</template>
2. script 部分
<script setup>
import { ref, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
const emit = defineEmits(["commitDatas"]);// 定义变量
const canvas = ref(null);
const graphics = ref(null);
const isDrawing = ref(false);
const curMouseX = ref(null);
const curMouseY = ref(null);
const isCommit = ref(false);
const content = ref(null);
const ifGraph = ref(false);
const ifSetController = ref(false);
const fontSize = ref(1);
const fontColorArray = ref(["#F59999", "#E86262", "#AA4446", "#6B4849", "#34231E", "#435772", "#2DA4A8", "#EFDCD3", "#FEAA3A", "#FD6041", "#CF2257", "#404040", "#92BEE2", "#2286D8"]);
const fontColor = ref("#000000");// 选择画笔颜色
const setImgColor = (curIndex) => {let liArray = curIndex.currentTarget.parentElement.children;for (let child of liArray) {child.className = "";}curIndex.currentTarget.className = "activeteLi";fontColor.value = curIndex.currentTarget.style.background;
};// 鼠标按下事件处理
const mousedown = (e) => {const rect = canvas.value.getBoundingClientRect();isDrawing.value = true;curMouseX.value = e.clientX - rect.left;curMouseY.value = e.clientY - rect.top;graphics.value.beginPath();graphics.value.moveTo(curMouseX.value, curMouseY.value);
};// 鼠标移动事件处理
const mousemove = (e) => {if (isDrawing.value) {const rect = canvas.value.getBoundingClientRect();graphics.value.strokeStyle = fontColor.value;graphics.value.lineWidth = fontSize.value;curMouseX.value = e.clientX - rect.left;curMouseY.value = e.clientY - rect.top;graphics.value.lineTo(curMouseX.value, curMouseY.value);graphics.value.stroke();ifGraph.value = true;}
};// 清除画布
const clear = () => {ifGraph.value = false;canvas.value.width = canvas.value.width;
};// 将 DataURL 转换为文件
const dataURLtoFile = (dataurl, filename) => {const arr = dataurl.split(",");const bstr = window.atob(arr[1]);let n = bstr.length;const u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}return new File([u8arr], filename, { type: "image/png" });
};// 提交签名
const commit = () => {if (!ifGraph.value) {ElMessage({ message: "没有可提交的内容!", type: "error", duration: 2000 });return;}ElMessageBox({title: "操作提示",message: "确定提交?",confirmButtonText: "确定",cancelButtonText: "取消",showCancelButton: true,closeOnClickModal: false,type: "warning",}).then(() => {isCommit.value = true;content.value = canvas.value.toDataURL();emit("commitDatas", dataURLtoFile(content.value, "签名"));});
};// 返回编辑模式
const goback = () => {ifGraph.value = false;canvas.value.width = canvas.value.width;isCommit.value = false;
};// 初始化画布
onMounted(() => {const cvs = document.getElementById("container");cvs.width = 800;cvs.height = 400;graphics.value = cvs.getContext("2d");canvas.value = cvs;document.addEventListener("mouseup", () => {isDrawing.value = false;graphics.value.closePath();});
});
</script>
3. 样式部分
<style>
.activeteLi {box-shadow: 0 0 3px rgb(0 0 0 / 95%);transform: scale(1.2);
}
</style>

解决鼠标和画笔偏移问题

当我们将签名组件放到 el-dialog 弹窗中时,出现鼠标点击位置与实际绘制位置不符的偏移问题。这是由于 canvas 相对于窗口的位置发生了变化。为了解决这个问题,我们使用了 getBoundingClientRect() 来动态计算 canvas 在页面中的位置,从而调整鼠标绘制的准确性。

javascript">const rect = canvas.value.getBoundingClientRect();
curMouseX.value = e.clientX - rect.left;
curMouseY.value = e.clientY - rect.top;

getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置,因此可以精确计算出鼠

标相对于 canvas 的位置,确保绘制效果不会偏移。


http://www.ppmy.cn/devtools/128033.html

相关文章

判断 HTTP/2 多路复用是否在服务器上实现

要判断 HTTP/2 多路复用是否在服务器上实现&#xff0c;并确保浏览器正在使用多路复用来加载资源&#xff0c;您可以使用以下几种方法进行验证&#xff1a; 1. 使用浏览器开发者工具 大多数现代浏览器&#xff08;如 Chrome、Firefox、Edge&#xff09;提供了开发者工具&…

Python进阶

面向对象编程&#xff08;OOP&#xff09; 1.1 类和对象 类 是一个模板&#xff0c;用来描述一类对象的属性和行为。通过定义类&#xff0c;你可以创建对象&#xff08;也称为类的实例&#xff09;。对象 是类的实例&#xff0c;通过类创建的具体实例对象。 示例&#xff1a…

基于Springboot在线视频网站的设计与实现

基于Springboot视频网站的设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;idea 源码获取&#xff1a;https://do…

架构设计笔记-21-案例分析

1.遗留系统策略 / 数据迁移 / REST和RPC风格 2.分布式系统 / 分布式对象调用 3.开放式架构 / GOA 4.ESB 5.FMEA故障分析 6. 加密 / 公钥体系机制 / 加解密API和透明加密 7.嵌入式系统故障 / 故障滤波算法 / 容错算法 8.开源框架struts / spring / Hibenate 9.企业应用集成 10.T…

西门子嵌入式面试题及参考答案(万字长文)

RAM 和 ROM 的各种总线协议 RAM(随机存取存储器)和 ROM(只读存储器)在嵌入式系统中起着重要的作用,它们通常使用不同的总线协议与其他设备进行通信。 一、RAM 的常见总线协议 SRAM(静态随机存取存储器)常用的总线协议有异步 SRAM 协议和同步 SRAM 协议。 异步 SRAM 协议…

2024软考网络工程师笔记 - 第10章.组网技术

文章目录 交换机基础1️⃣交换机分类2️⃣其他分类方式3️⃣级联和堆叠4️⃣堆叠优劣势5️⃣交换机性能参数 &#x1f551;路由器基础1️⃣路由器接口2️⃣交换机路由器管理方式2️⃣交换机路由器管理方式 交换机基础 1️⃣交换机分类 1.根据交换方式分 存储转发式交换(Store…

【MySQL】InnoDB存储引擎中的锁

实现事务隔离级别的过程中用到了锁&#xff0c;所谓锁就是在事务A修改某些数据时&#xff0c;对这些数据加一把锁&#xff0c;防止其他事务同时对这些数据执行修改操作;当事务A完成修改操作后&#xff0c;释放当前持有的锁&#xff0c;以便其他事务再次上锁执行对应的操作。不同…

【三】企业级JavaScript开发之手册与规范

规范 ECMA-262 规范 包含了大部分深入的、详细的、规范化的关于 JavaScript 的信息。这份规范明确地定义了这门语言。 但正因其规范化&#xff0c;对于新手来说难以理解。所以&#xff0c;如果你需要关于这门语言细节最权威的信息来源&#xff0c;这份规范就很适合你&#xf…