首页 > 其他分享 >canvas实现签名

canvas实现签名

时间:2023-08-22 14:00:09浏览次数:53  
标签:canvas const 实现 ctx number 签名 startX event

在开源项目中发现canvas实现签名功能以此记录:http://www.youlai.tech/pages/52d5c3/

HTML:

<div class="canvas-dom">
    <el-button plain type="text" style="margin-left:20px;margin-top:20px;font-size:18px;" @click="back">返回</el-button>
    <canvas ref="canvas" height="200" width="500" @mousedown="onEventStart" @mousemove.stop.prevent="onEventMove"
      @mouseup="onEventEnd" @touchstart="onEventStart" @touchmove.stop.prevent="onEventMove" @touchend="onEventEnd">
    </canvas>
    <header>
      <el-button type="primary" @click="handleSaveImg">保存为图片</el-button>
      <el-button @click="handleToFile"> 保存到后端 </el-button>
      <el-button @click="handleClearSign"> 清空签名 </el-button>
    </header>
    <img v-if="imgUrl" :src="imgUrl" alt="签名" />
  </div>

 script:

 

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
// import { uploadFileApi } from "@/api/file"

const imgUrl = ref("")
const canvas = ref()
let ctx: CanvasRenderingContext2D

// 正在绘制中,用来控制 move 和 end 事件
let painting = false

// 获取触发点相对被触发dom的左、上角距离
const getOffset = (event: MouseEvent | TouchEvent) => {
  let offset: [number, number]
  if ((event as MouseEvent).offsetX) {
    // pc端
    const { offsetX, offsetY } = event as MouseEvent;
    offset = [offsetX, offsetY]
  } else {
    // 移动端
    const { top, left } = canvas.value.getBoundingClientRect()
    const offsetX = (event as TouchEvent).touches[0].clientX - left
    const offsetY = (event as TouchEvent).touches[0].clientY - top
    offset = [offsetX, offsetY]
  }

  return offset;
};

// 绘制起点
let startX = 0, startY = 0

// 鼠标/触摸 按下时,保存 触发点相对被触发dom的左、上 距离
const onEventStart = (event: MouseEvent | TouchEvent) => {
  [startX, startY] = getOffset(event)
  painting = true
}

const onEventMove = (event: MouseEvent | TouchEvent) => {
  if (painting) {
    // 鼠标/触摸 移动时,保存 移动点相对 被触发dom的左、上 距离
    const [endX, endY] = getOffset(event)
    paint(startX, startY, endX, endY, ctx)

    // 每次绘制 或 清除结束后,起点要重置为上次的终点
    startX = endX
    startY = endY
  }
};

const onEventEnd = () => {
  if (painting) {
    painting = false; // 停止绘制
  }
};

onMounted(() => {
  ctx = canvas.value.getContext("2d") as CanvasRenderingContext2D
});
const handleToFile = async () => {
  if (isCanvasBlank(canvas.value)) {
    ElMessage({
      type: "warning",
      message: "当前签名文件为空",
    })
    return
  }
  const file = dataURLtoFile(canvas.value.toDataURL(), "签名.png")

  if (!file) return
  // 文件上传后端,返回地址,显示
  // const { data } = await uploadFileApi(file)
  // handleClearSign();
  // imgUrl.value = data.url
};
const handleClearSign = () => {
  ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
};
const isCanvasBlank = (canvas: HTMLCanvasElement) => {
  const blank = document.createElement("canvas"); //系统获取一个空canvas对象
  blank.width = canvas.width;
  blank.height = canvas.height;
  return canvas.toDataURL() == blank.toDataURL(); //比较值相等则为空
};

// 保存为图片
const handleSaveImg = () => {
  if (isCanvasBlank(canvas.value)) {
    ElMessage({
      type: "warning",
      message: "当前签名文件为空",
    })
    return
  }
  const el = document.createElement("a")
  // 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式
  el.href = canvas.value.toDataURL()
  el.download = "签名";
  // 创建一个点击事件并对 a 标签进行触发
  const event = new MouseEvent("click")
  el.dispatchEvent(event);
}

// 转为file格式,可传递给后端
const dataURLtoFile = (dataurl: string, filename: string) => {
  const arr: string[] = dataurl.split(",")
  if (!arr.length) return

  const mime = arr[0].match(/:(.*?);/)
  if (mime) {
    const bstr = 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: mime[1] })
  }
};
// canvas 画图
function paint(
  startX: number,
  startY: number,
  endX: number,
  endY: number,
  ctx: CanvasRenderingContext2D
) {
  ctx.beginPath()
  ctx.globalAlpha = 1
  ctx.lineWidth = 2
  ctx.strokeStyle = "#000"
  ctx.moveTo(startX, startY)
  ctx.lineTo(endX, endY)
  ctx.closePath()
  ctx.stroke()
}

// 橡皮
function eraser(
  startX: number,
  startY: number,
  endX: number,
  endY: number,
  ctx: CanvasRenderingContext2D,
  size: number,
  shape: "rect" | "circle"
) {
  ctx.beginPath()
  ctx.globalAlpha = 1
  switch (shape) {
    case "rect":
      ctx.lineWidth = size
      ctx.strokeStyle = "#fff"
      ctx.moveTo(startX, startY)
      ctx.lineTo(endX, endY)
      ctx.closePath()
      ctx.stroke()
      break
    case "circle":
      ctx.fillStyle = "#fff";
      ctx.arc(startX, startY, size, 0, 2 * Math.PI)
      ctx.fill()
      break
  }
}
const back = () => {
  window.history.back()
}
</script>

css:

<style scoped lang="scss">
.canvas-dom {
  width: 100%;
  height: 100%;
  padding: 0 20px;
  background-color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  canvas {
    border: 1px solid #e6e6e6;
  }

  header {
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    margin: 8px;

    .eraser-option {
      display: flex;

      label {
        white-space: nowrap;
      }
    }
  }
}
</style>

代码地址:https://gitee.com/yuexiayunsheng/vue3learn/blob/master/src/views/Signature.vue

标签:canvas,const,实现,ctx,number,签名,startX,event
From: https://www.cnblogs.com/foxing/p/17648337.html

相关文章

  • java实现大文件上传代码
    ​ 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数下面直接贴代码吧,一些难懂的我大部分都加上注释了:上传文件实体类:看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。pub......
  • 基于JAVA+MySQL技术智能服装推荐系统的设计与实现-计算机毕业设计源码+LW文档
    1.开题依据1.1研究的目的意义在过去到现在,消费方式从物物交换到以通俗认知中的“货币”购买物品,再到如今的网上支付交易,实物物流运输到达我们的手上。购物方式从实体店的消费模式,转到了网上店铺的交易。相信很多人在现实生活中都有过实体店购物的消费的体验,在实体店消费需要安排......
  • Java实现灰度发布的常用方式
    以下内容由GPT3.5生成,仅用于参考并了解基本概念什么是灰度发布灰度发布(GrayDeployment),也称为渐进式发布或金丝雀发布,是一种软件发布策略,用于将新版本的软件或功能逐步引入生产环境,以降低可能出现的问题对整个系统的影响。这种发布方式的核心思想是将新功能或版本仅部分暴......
  • 基于Hive数仓实现需求开发
    1、建库建表与加载数据上传到HDFS,即加载数据,可以使用命令行进行上传,还可以直接在网页里面进行上传;在DataGrip软件里面,将tsv文件复制进去,然后将语法转换成Hive语法,并连接Hive;写入建库建表语句;之后利用语句将数据加载到表中;2、ETL数据清洗1、有些字段为空,数据不合法涉及到......
  • python @property装饰器实现原理
    @property装饰器可以使一个对象的方法变成属性访问,比较方便,那么它是如何实现的呢?下面是一个自己动手实现的例子:classMyProperty:def__init__(self,fget=None,fset=None):self.fget=fgetself.fset=fsetdef__get__(self,instance,o......
  • knn 算法的实现原理是怎样的
    K最近邻(K-NearestNeighbors,简称KNN)算法是一种用于分类和回归的基本机器学习算法。其原理是基于样本之间的距离度量,通过找出离待预测样本最近的K个训练样本,利用这K个样本的标签信息进行分类或回归预测。主要思想就是物以类聚人以群分的思想,关键就是KNN中K近邻中K的确定,和距离的定义......
  • java实现大文件上传源码
    ​ 我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。 首先我们需要了解的是上传文件三要素:1.表单提交方式:post(get方式提交有大小限制,post没有)2.表单的enctype属性:必须设置为multipart/form-data.3.表单......
  • vue实现‘换一批’功能
    实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组<divclass="flex-align"@click="change"><span>换一批</span></div>data(){return{qList:[],//处理后的list......
  • 炫酷loading css实现
    实现效果代码本文使用react实现,其他同理index.jsimportReactfrom'react';importsfrom'./index.less';exportdefaultfunctionLoading(){return(<divclassName={`${s['loading-container']}${s['loading-active'......
  • SpringBoot实现统一异常处理
    大家在使用SpringBoot开发项目的时候肯定都需要处理异常吧,没有处理异常那么异常信息直接显示给用户这是非常不雅观的,同时还可能造成用户误会,那么今天我们就来简单的写一下如何在SpringBoot项目中实现统一的异常处理。1.自定义异常类我们先定义一个自定义业务异常类,这个异常类继......