• 详情讲解canvas实现电子签名


    签名的实现功能

    我们要实现签名:
    1.我们首先要鼠标按下,移动,抬起。经过这三个步骤。
    我们可以实现一笔或者连笔。
    按下的时候我们需要移动画笔,可以使用 moveTo 来移动画笔。
    e.pageX,e.pageY来获取坐标位置
    移动的时候我们进行绘制 
    ctx.lineTo(e.pageX,e.pageY)   
    ctx.stroke()
    通过开关flag来判断是否绘制
    
    2.我们可以调整画笔的粗细
    3.当我们写错的时候,可以撤回上一步
    4.重置整个画板
    5.点击保存的时候,可以生成一张图片
    6.base64转化为file
    

    实现签名

    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>Documenttitle>
        <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"> 
    来调整画笔。
    因为我们我们调整画笔后,线条的大小就会发生改变。
    因此我们在每次按下的时候都需要开始本次绘画。
    抬起的时候结束本次绘画,
    这样才能让不影响上一次画笔的大小。
    
    核心的代码
    "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();
      });
    }
    

    重置整个画布

    
    
    // 重置整个画布
    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)
    })
    

    全部代码

    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>Documenttitle>
        <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">转化为filebutton>
          <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>
    
  • 相关阅读:
    php实现图片加法验证码
    【VUE】vue程序设计----模仿网易严选
    https和http的区别和优势
    目标检测(8)—— YOLOV5代码调试及参数解析
    <html dir=ltr>是什么意思?
    内存与IO访问函数实例
    论文解读(AGCN)《 Attention-driven Graph Clustering Network》
    list的模拟实现
    C++学习之多态详解
    极智AI | 讲解 TensoRT Activation 算子
  • 原文地址:https://www.cnblogs.com/IwishIcould/p/17644695.html