详情讲解canvas实现电子签名

简介: 详情讲解canvas实现电子签名

签名的实现功能

我们要实现签名:
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>

遇见问题,这是你成长的机会,如果你能够解决,这就是收获。

相关文章
|
4月前
Uniapp 电子签名 包含 返回 撤回 颜色 线条等功能
Uniapp 电子签名 包含 返回 撤回 颜色 线条等功能
23 0
|
3月前
|
运维 小程序 API
社区每周丨支付宝小程序导航栏升级及人脸认证计费规则更新
社区每周丨支付宝小程序导航栏升级及人脸认证计费规则更新
41 0
|
4月前
|
小程序
医疗设备管理二维码:扫码查看使用说明、填写消毒记录
在草料上,简单修改模板,即可为每个医疗仪器都生成一个二维码,汇总使用、洗消、保养、校准等环节。护理人员扫码就能查看设备信息、操作规范,还可以填写和查看使用、消毒记录。如果仪器出现故障,扫码即可报修、更新维修记录。
|
5月前
|
缓存 DataWorks 定位技术
在DataWorks中,即使表权限申请已经通过审批,也可能因为某些原因导致无法正常查看数据地图的预览功能
在DataWorks中,即使表权限申请已经通过审批,也可能因为某些原因导致无法正常查看数据地图的预览功能
37 2
|
6月前
|
小程序
如何通过二维码展示产品信息?
在草料二维码上搭建产品信息介绍系统,在二维码上展示图片、文字、音视频等宣传介绍内容,将二维码印制在产品实物或包装物料上,客户只需要用微信扫描二维码,即可随时随地查看图文并茂的介绍。
|
10月前
|
前端开发 JavaScript
使用Canvas实现电子签名功能
电子签名是在数字环境中代替传统纸质签名的一种方式,它在许多应用中都得到了广泛使用,如电子合同、电子表单等。在本文中,我们将使用HTML的Canvas元素和JavaScript来实现一个简单的电子签名功能,用户可以通过鼠标或触摸屏幕来绘制自己的签名。
257 0
|
11月前
|
前端开发 小程序 JavaScript
|
11月前
|
XML JSON 缓存
阿里巴巴国际站获得商品详情 API 调用分享(销量、详情图片、宝贝链接)
阿里巴巴国际站获得商品详情 API 调用分享(销量、详情图片、宝贝链接)
|
数据可视化 安全 数据挖掘
「教程」天气预警 API 详解:申请密钥到接入代码一气呵成!
天气预警 API 作为一种新型的数据接口,为开发者和应用提供了方便的获取天气预警数据的方式。通过该 API ,可以获取指定城市当前生效中的各类天气预警信息,例如暴雨、雷电、台风等。预警数据来自国家预警中心,保证了数据的高质量和实时性。
318 0
|
小程序
产品信息二维码应用方案
为每一类产品生成一个二维码,用于展示文字、图片、音视频等产品信息。将二维码印刷在产品外包装、画册、样品卡或说明书上,用户通过微信扫码就能查看图文并茂的产品介绍、操作教程,并获取售后服务。
127 0
产品信息二维码应用方案