首页 > 其他分享 >详情讲解canvas实现电子签名

详情讲解canvas实现电子签名

时间:2023-08-23 09:22:05浏览次数:42  
标签:canvas 画笔 ctx 电子签名 按下 let 事件 讲解

签名的实现功能

我们要实现签名:
1.我们首先要鼠标按下,移动,抬起。经过这三个步骤。
我们可以实现一笔或者连笔。
按下的时候我们需要移动画笔,可以使用 moveTo 来移动画笔。
e.pageX,e.pageY来获取坐标位置
移动的时候我们进行绘制 
ctx.lineTo(e.pageX,e.pageY)   
ctx.stroke()
通过开关flag来判断是否绘制

2.我们可以调整画笔的粗细
3.当我们写错的时候,可以撤回上一步
4.重置整个画板
5.点击保存的时候,可以生成一张图片
6.base64转化为file

实现签名

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      *{
        padding: 0;
        margin: 0;
      }
      #canvas {
        border: 2px dotted #ccc;
        background-repeat: no-repeat;
        background-size: 80px;
      }
    </style>
</head>
<body>
    <div class="con-box">
      <canvas id="canvas" width="600px" height="400px"></canvas>
      <button id="save-btn" onclick="saveHandler">保存</button>
      <button id="reset-btn" onclick="resetHandler">重置</button>
    </div>
</body>
<script>
// 获取canvas元素的DOM对象 
const canvas=document.getElementById('canvas')
// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')
// 笔画内容的颜色,一般为黑色
ctx.strokeStyle='#000'
let flag= false
// 注册鼠标按下事件
canvas.addEventListener('mousedown',e=>{
  console.log('按下',e.pageX,e.pageY)
  flag=true
  // 获取按下的那一刻鼠标的坐标,同时移动画笔
  ctx.moveTo(e.pageX,e.pageY)
})
// 注册移动事件
canvas.addEventListener('mousemove',e=>{
  console.log('移动')
  if(flag){
    // 使用直线连接路径的终点 x,y 坐标的方法(并不会真正地绘制)
    ctx.lineTo(e.pageX,e.pageY)
    // 使用 stroke() 方法真正地画线
    ctx.stroke()
  }
})
// 注册抬起事件
canvas.addEventListener('mouseup',e=>{
  console.log('抬起')
  flag=false
})
</script>
</html>

鼠标移入canvas就会触发事件

通过上面的图,我们发现了一个点。
那就是鼠标移入canvas所在的区域。
就会触发移动事件的代码。
这是为什么呢?
因为我们在移入的时候注册了事件,因此就会触发。
现在我们需要优化一下:将移动事件,抬起事件放在按下事件里面
同时,当鼠标抬起的时候,移除移动事件和抬起事件。【不移除按下事件】
这里可能有的小伙伴会问?
为什么抬起的时候不移除按下事件。
因为:代码从上往下执行,当我们移除抬起事件后,我们只能绘画一次了。
当我们移除事件时,我们就不需要开关 flag 了。
删除flag的相关代码
<script>
// 获取canvas元素的DOM对象 
const canvas=document.getElementById('canvas')
// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')
// 笔画内容的颜色,一般为黑色
ctx.strokeStyle='#000'

// 注册鼠标按下事件
canvas.addEventListener('mousedown',mousedownFun)

// 按下事件
function mousedownFun(e){
  console.log('按下',e.pageX,e.pageY)
  // 获取按下的那一刻鼠标的坐标,同时移动画笔
  ctx.moveTo(e.pageX,e.pageY)
  // 注册移动事件
  canvas.addEventListener('mousemove',mousemoveFun)
  // 注册抬起事件
  canvas.addEventListener('mouseup',mouseupFun)
}

// 移动事件
function mousemoveFun(e){
  console.log('移动')
  // 使用直线连接路径的终点 x,y 坐标的方法(并不会真正地绘制)
  ctx.lineTo(e.pageX,e.pageY)
  // 使用 stroke() 方法真正地画线
  ctx.stroke()
}

// 抬起事件
function mouseupFun(e){
  console.log('抬起')
  // 移除移动事件
  canvas.removeEventListener('mousemove', mousemoveFun)
  // 移除抬起事件
  canvas.removeEventListener('mouseup', mouseupFun)
}
</script>

发现bug-鼠标不按下也可以绘制笔画

我们发现鼠标移出canvas所在区域后。
然后在移入进来,鼠标仍然可以进行绘制。(此时鼠标已经是松开了)
这很明显是一个bug。这个bug产生的原因在于:
鼠标移出canvas所在区域后没有移出移动事件
// 鼠标移出canvas所在的区域事件-处理鼠标移出canvas所在区域后
// 然后移入不按下鼠标也可以绘制笔画
canvas.addEventListener('mouseout',e=>{
  // 移除移动事件
  canvas.removeEventListener('mousemove', mousemoveFun)
})

如何设置画笔的粗细

我们想要调整画笔的粗细。
需要使用 ctx.lineWidth 属性来设置画笔的大小默认是1。
我们用   <input type="range" class="range" min="1" max="30" value="1" id="range"> 
来调整画笔。
因为我们我们调整画笔后,线条的大小就会发生改变。
因此我们在每次按下的时候都需要开始本次绘画。
抬起的时候结束本次绘画,
这样才能让不影响上一次画笔的大小。
核心的代码
<input type="range" class="range" min="1" max="30" value="1" id="range">


// 获取设置画笔粗细的dom元素
let range = document.querySelector("#range");
// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')


// 按下事件
function mousedownFun(e){
  console.log('按下',e.pageX,e.pageY)
  // 开始本次绘画(与画笔大小设置有关)
  ctx.beginPath();
  // 设置画笔的粗细
  ctx.lineWidth = range.value || 1
}

// 抬起事件
function mouseupFun(e){
  // 结束本次绘画(与画笔大小设置有关)
  ctx.closePath();
  console.log('抬起')
}

撤回上一步

1. 先声明一个数组. let historyArr=[]
  按下的时候记录当前笔画起始点的特征(颜色 粗细 位置)
  currentPath = {
    color: ctx.strokeStyle,
    width: ctx.lineWidth,
    points: [{ x: e.offsetX, y: e.offsetY }]
  }

2.按下移动的时候记录每一个坐标点[点连成线]
currentPath.points.push({ x: e.offsetX, y: e.offsetY });

3.鼠标抬起的时候说明完成了一笔(连笔)
  historyArr.push(currentPath);

4.点击撤销按钮的时候删除最后一笔
5.然后重新绘制之前存储的画笔
<!-- 核心代码 -->
<button id="revoke">撤销</button>

let historyArr = [] //保存所有的操作
let currentPath = null;

let revoke=document.querySelector("#revoke");

// 按下事件
function mousedownFun(e){
  // 开始本次绘画(与画笔大小设置有关)
  ctx.beginPath();
  // 设置画笔的粗细
  ctx.lineWidth = range.value || 1
  // 获取按下的那一刻鼠标的坐标,同时移动画笔
  ctx.moveTo(e.pageX,e.pageY)

  // 记录当前笔画起始点的特征(颜色 粗细 位置)
  currentPath = {
    color: ctx.strokeStyle,
    width: ctx.lineWidth,
    points: [{ x: e.offsetX, y: e.offsetY }]
  }
}

// 移动事件
function mousemoveFun(e){
  ctx.lineTo(e.pageX,e.pageY)
  currentPath.points.push({ x: e.offsetX, y: e.offsetY });
  ctx.stroke()
}

// 抬起事件
function mouseupFun(e){
  historyArr.push(currentPath);
  ctx.closePath();
}

// 撤销按钮点击事件
revoke.addEventListener('click', e => {
  if (historyArr.length === 0) return;
  // 删除最后一条的记录
  historyArr.pop()
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawPaths(historyArr);
});

// 画所有的路径
function drawPaths(paths) {
  paths.forEach(path => {
    ctx.beginPath();
    ctx.strokeStyle = path.color;
    ctx.lineWidth = path.width;
    ctx.moveTo(path.points[0].x, path.points[0].y);
    // path.points.slice(1) 少画 与  path.points 区别是少画一笔和正常笔数
    console.log('path',path)
    path.points.slice(1).forEach(point => {
      ctx.lineTo(point.x, point.y);
    });
    ctx.stroke();
  });
}

重置整个画布

<button id="reset" >重置</button>

// 重置整个画布
reset.addEventListener('click',e=>{
  //清空整个画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);
})

ps:清空画布的主要运用了ctx.clearRect这个方法

保存

保存图片主要是通过 canvas.toDataURL 生成的是base64
然后通过a标签进行下载
saveBtn.addEventListener('click',()=>{
  let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
  let link = document.createElement('a');
  link.download = "tupian";
  link.href = imgURL;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
})

生成file文件发送给后端

// base64转化为file文件
function base64changeFile (urlData, fileName) {
  // split将按照','字符串按照,分割成一个数组,
  // 这个数组通常包含了数据类型(MIME type)和实际的数据。
  // 数组的第1项是类型 第2项是数据
  const arr = urlData.split(',')
  // data:image/png;base64
  const mimeType = arr[0].match(/:(.*?);/)[1]
  console.log('类型',mimeType)
  // 将base64编码的数据转换为普通字符串
  const bytes = atob(arr[1])
  let n = bytes.length
  // 创建了一个新的Uint8Array对象,并将这些字节复制到这个对象中。
  const fileFormat = new Uint8Array(n)
  while (n--) {
    fileFormat[n] = bytes.charCodeAt(n)
  }
  return new File([fileFormat], fileName, { type: mimeType })
}

fileBtn.addEventListener('click',()=>{
  let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
  let file = base64changeFile(imgURL,'qianMing')
  console.log('file',file)
})

全部代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      *{
        padding: 0;
        margin: 0;
      }
      #canvas {
        border: 2px dotted #ccc;
      }
    </style>
</head>
<body>
    <div class="con-box">
      <canvas id="canvas" width="600px" height="400px"></canvas>
      <input type="range" class="range" min="1" max="30" value="1" id="range">
      <button id="revoke">撤销</button>
      <button id="save-btn">保存</button>
      <button id="file">转化为file</button>
      <button id="reset" >重置</button>
    </div>
</body>
<script>
// 获取canvas元素的DOM对象 
const canvas=document.getElementById('canvas')
// 获取设置画笔粗细的dom元素
let range = document.querySelector("#range");
let revoke=document.querySelector("#revoke");
let reset=document.querySelector("#reset");
let saveBtn=document.querySelector("#save-btn");
let fileBtn=document.querySelector("#file");


// 获取渲染上下文和它的绘画功能
const ctx= canvas.getContext('2d')
// 笔画内容的颜色,一般为黑色
ctx.strokeStyle='#000'

let historyArr = [] //保存所有的操作
let currentPath = null;

// 注册鼠标按下事件
canvas.addEventListener('mousedown',mousedownFun)

// 按下事件
function mousedownFun(e){
  console.log('按下',e.pageX,e.pageY)
  // 开始本次绘画(与画笔大小设置有关)
  ctx.beginPath();
  // 设置画笔的粗细
  ctx.lineWidth = range.value || 1
  // 获取按下的那一刻鼠标的坐标,同时移动画笔
  ctx.moveTo(e.pageX,e.pageY)

  // 记录当前笔画起始点的特征(颜色 粗细 位置)
  currentPath = {
    color: ctx.strokeStyle,
    width: ctx.lineWidth,
    points: [{ x: e.offsetX, y: e.offsetY }]
  }

  // 注册移动事件
  canvas.addEventListener('mousemove',mousemoveFun)
  // 注册抬起事件
  canvas.addEventListener('mouseup',mouseupFun)
}

// 移动事件
function mousemoveFun(e){
  console.log('移动')
  // 使用直线连接路径的终点 x,y 坐标的方法(并不会真正地绘制)
  ctx.lineTo(e.pageX,e.pageY)
  // 记录画笔的移动的每一个坐标位置
  currentPath.points.push({ x: e.offsetX, y: e.offsetY });
  // 使用 stroke() 方法真正地画线
  ctx.stroke()
}

// 抬起事件
function mouseupFun(e){
  // 一笔结束后存储起来
  historyArr.push(currentPath);
  console.log('historyArr',historyArr)
  // 结束本次绘画(与画笔大小设置有关)
  ctx.closePath();
  console.log('抬起')
  // 移除移动事件
  canvas.removeEventListener('mousemove', mousemoveFun)
  // 移除抬起事件
  canvas.removeEventListener('mouseup', mouseupFun)
}

// 鼠标移出canvas所在的区域事件-处理鼠标移出canvas所在区域后,然后移入不按下鼠标也可以绘制笔画
canvas.addEventListener('mouseout',e=>{
  // 移除移动事件
  canvas.removeEventListener('mousemove', mousemoveFun)
})


  // 撤销按钮点击事件
revoke.addEventListener('click', e => {
  if (historyArr.length === 0) return;
  // 删除最后一条的记录
  historyArr.pop()
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawPaths(historyArr);
});

// 重置整个画布
reset.addEventListener('click',e=>{
  //清空整个画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);
})

// 保存为图片
saveBtn.addEventListener('click',()=>{
  let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
  console.log('imgURL',imgURL)
  let link = document.createElement('a');
  link.download = "tupian";
  link.href = imgURL;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
})

// 画所有的路径
function drawPaths(paths) {
  console.log(11,paths)
  paths.forEach(path => {
    ctx.beginPath();
    ctx.strokeStyle = path.color;
    ctx.lineWidth = path.width;
    ctx.moveTo(path.points[0].x, path.points[0].y);
    // path.points.slice(1) 少画 与  path.points 区别是少画一笔和正常笔数
    console.log('path',path)
    path.points.slice(1).forEach(point => {
      ctx.lineTo(point.x, point.y);
    });
    ctx.stroke();
  });
}

// base64转化为file文件
function base64changeFile (urlData, fileName) {
  // split将按照','字符串按照,分割成一个数组,
  // 这个数组通常包含了数据类型(MIME type)和实际的数据。
  // 数组的第1项是类型 第2项是数据
  const arr = urlData.split(',')
  // data:image/png;base64
  const mimeType = arr[0].match(/:(.*?);/)[1]
  console.log('类型',mimeType)
  // 将base64编码的数据转换为普通字符串
  const bytes = atob(arr[1])
  let n = bytes.length
  // 创建了一个新的Uint8Array对象,并将这些字节复制到这个对象中。
  const fileFormat = new Uint8Array(n)
  while (n--) {
    fileFormat[n] = bytes.charCodeAt(n)
  }
  return new File([fileFormat], fileName, { type: mimeType })
}

fileBtn.addEventListener('click',()=>{
  let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:600, height:400});
  let file = base64changeFile(imgURL,'qianMing')
  console.log('file',file)
})
</script>
</html>

标签:canvas,画笔,ctx,电子签名,按下,let,事件,讲解
From: https://www.cnblogs.com/IwishIcould/p/17644695.html

相关文章

  • canvas实现签名
    在开源项目中发现canvas实现签名功能以此记录:http://www.youlai.tech/pages/52d5c3/HTML:<divclass="canvas-dom"><el-buttonplaintype="text"style="margin-left:20px;margin-top:20px;font-size:18px;"@click="back">返回<......
  • 直播平台源码搭建协议讲解篇:传输控制协议TCP
    简介:由于直播平台在当今时代发展的越来越迅速,使得直播平台的技术功能越来越智能,让用户在直播平台中能够和其他用户进行实时互动,让用户可以获取到全世界最新的资讯,让一些用户可以作为主播获得工作,让另一些用户作为观众获得放松快乐等,所以直播平台源码搭建也成为了众多创业者想要涉......
  • canvas实现图片加水印
    前言:有时候需要在一张图片上添加文字水印,形成新的图片,便于1vs多的分享。html中:<imgsrc=""alt=""id="newImg"/><canvasid="myCanvas"class="hidden"></canvas>js中:引入jquery.qrcode.min.js后functionImg(){//图片加......
  • 2023-08-21 canvas之fillText如何换行
    canvas的文本绘制:ctx.fillText('这是一段需要换行的内容啦啦啦啦啦啦啦啦',0,0);换行方式1:1、设置最大宽度:100(具体根据业务来定);ctx.fillText('这是一段需要换行的内容啦啦啦啦啦啦啦啦',0,0,100);2、判断要显示的文字内容是否超出100的长度,超出就截取一下,把超出的内容再......
  • 表格中的table-layout属性讲解
    定义和用法table-layout属性用来显示表格单元格、行、列的算法规则。table-layout有三个属性值:auto、fixed、inherit。fixed:固定表格布局固定表格布局与自动表格布局相比,允许浏览器更快地对表格进行布局。在固定表格布局中,水平布局仅取决于表格宽度、列宽度、表格边框宽度......
  • DPDK-22.11.2 [三] 官方helloworld编译运行讲解
    先安装dpdk编译完成后,先运行ninjainstall把相关内容安装到指定目录。ls/home/dpdkinstallbinincludelib64sharebin——一些脚本(用于绑定驱动等),编译的测试程序,编译的常用工具include——需要的头文件lib64——编译的类库share——文档相关......
  • Canvas绘制毛玻璃背景分享海报
    最近重新设计了分享海报,用毛玻璃作为背景,使整体更有质感,如果没有用到canvas,毛玻璃效果其实很好实现,给元素添加一个滤镜即可(比如:filter:blur(32px)),但是实践的过程中发现,canvas在IOS端一直没有效果,查了一个文档发现IOS端不支持filter。。。有点想骂人。。(PS:微信官方有关CanvasRende......
  • CSP-J 模拟赛 C 题讲解
    前言鸣谢:感谢LHT大佬的推荐、GCK大佬的提醒以及LBJ大佬帮我接龙。原题链接随手给大家扔一份吧。题目大意给你一个\(1\)到\(n\)的数列,当\(a_i<a_{i-1}\)的时候(大于号和小于号其实不用管,因为最后所有方案都会遍历到,对答案没有影响)放置一个红色灯笼,否则放置一个绿......
  • java基础类讲解
    一、Calendar类packagecom.qf.chapter_01;importjava.util.Calendar;publicclassTestCalendar{ publicstaticvoidmain(String[]args){ //创建Calendar对象 Calendarcalendar=Calendar.getInstance();//得到当前时间 System.out.println(calendar.getTime().......
  • 超实用的 linux atop 与 htop 监控工具讲解与实战操作
    目录一、概述1)atop概2)htop概述二、top,atop和htop对比1)top2)atop3)htop三、atop与htop监控工具安装四、atop与htop命令的基本语法1)atop2)htop一、概述atop和htop都是Linux系统上用于监控系统资源和进程活动的命令行工具,但它们有不同的特点和用途。atop实时监控示......