当前位置: 首页 > article >正文

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

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

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

电子签名组件功能

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

源代码实现

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

1. template 模板部分
<template>
  <!-- 电子签名组件 -->
  <div>
    <!-- 画布对象 -->
    <div
      ref="grapDiv"
      style="width: 800px; height: 400px; border: 2px dotted #ddd; border-radius: 10px;"
      v-show="!isCommit"
    >
      <canvas
        ref="grapCvs"
        id="container"
        @mousedown="mousedown"
        @mousemove="mousemove"
      />
    </div>

    <!-- 设置面板 -->
    <div
      ref="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 在页面中的位置,从而调整鼠标绘制的准确性。

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

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

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


http://www.kler.cn/news/361231.html

相关文章:

  • 保姆级VsCode配置C++编译环境
  • Java Swing的优秀开源项目学习推荐(UI、数据结构与设计模式)
  • Imagic: Text-Based Real Image Editing with Diffusion Models
  • 用.NET开发跨平台应用程序采用 Avalonia 与MAUI如何选择
  • java多态
  • 告别装机烦恼,IT小白到IT大神都在用的免费神器
  • C语言初阶小练习4(不用临时变量交换数值)
  • Ubuntu(22.04)本地部署Appsmith
  • Flink Taskmanager 内存模型详解
  • 大数据新视界 --大数据大厂之大数据与区块链双链驱动:构建可信数据生态
  • Android EditText调起键盘,阻止Recyclerview调整大小方法
  • 【Python】Playwright:环境配置与自动生成代码
  • 一、rpm命令,二、yum命令
  • 力扣——用栈实现队列(C语言)
  • CryoEM - 冷冻电镜 基于深度学习的 从头重构(Ab-initio Reconstruction) 开源项目 教程
  • Redis 哨兵与集群:高可用与可扩展的解决方案
  • 2.3 朴素贝叶斯(基础分类)
  • C语言数据结构之双向链表(LIST)的实现
  • 独立构件风格
  • 二分图染色法
  • 帝国CMS – AutoTitlePic 自动生成文章标题图片插件
  • Centos7 安装 Openssl 和 Nginx
  • 微分方程(Blanchard Differential Equations 4th)中文版Exercise 1.4
  • postgresql14主从同步流复制搭建
  • 跨域问题和前端攻击
  • 【开源免费】基于SpringBoot+Vue.JS母婴商城系统 (JAVA毕业设计)