canvas实现手动签名包含(原生js和vue实现)

145 阅读5分钟

canvas实现手动签名包含(原生js和vue实现)

如果想要实现手动在画布上书写或者画画,需要学会canvas的画路径,这里我就不介绍了。 现在我们先说明一下签名的功能 当鼠标按下之后,开始移动鼠标就可以根据鼠标移动画出一条路径,然后弹起鼠标之后,就停止绘画

从上面的使用过程中我们可以设计实现的思路:

  1. 需要监听鼠标的按下和弹起事件,记录鼠标的状态,并且鼠标弹起和按下改变鼠标的状态
  2. 鼠标需要监测鼠标在画布第一次按下的时候的坐标
  3. 弹起鼠标和鼠标移动到画布外在canvas画布上就不能绘图,就将坐标重置
  4. 监听鼠标在画布上的移动,需要监听canvas画布的鼠标移动事件前提是鼠标为按下状态
  5. 除了第一次每次移动就将移动终点作为新起点,然后依次往后画出路径
  6. 需要判断画布的边界,当鼠标移动出去之后,如果鼠标还是按压状态,进入之后就要开始绘图

原生js实现

根据上述

第一步 首先需要一个全局变量来设置鼠标的按下和弹起状态mouseStatus,需要将鼠标进入画布的坐标startX,startY 获取画布信息

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box{
      width: 600px;
      border: 1px solid #000;
    }
    #canvas:active {
      cursor: pointer;
    }
    .img-box{
      background-color: skyblue;
      width: 200px;
      height: 200px;
    }
    .img-box img{
      width: 100%;
      height: 100%;
    }
  </style>
</head>

<body>
  <div class="box">
    <canvas id="canvas" width="600" height="400"></canvas>  
  </div>
  <button class="btn">保存</button>
  <button class="clear">清除</button>
  <div class="img-box">
    
  </div>
  <script>
    /** @type {HTMLCanvasElement}**/
      const canvas=document.querySelector("#canvas")
  const ctx=canvas.getContext("2d")
  let mouseStatus=false//默认是弹起状态
  let startX=-1
  let startY=-1
  document.addEventListener("mousedown",function (e) {
        mouseStatus=true
    })
  document.addEventListener("mouseup",function () {
      mouseStatus=false
    })

  </script>
</body>

</html>

第二步 记录鼠标状态为按下并且第一次进入canvas画布的坐标(startX,startY) 第一种情况:在canvas画布区域按下鼠标

canvas.addEventListener("mousedown",function(e){
         startX=e.offsetX
         startY=e.offsetY
    })

第二种情况:鼠标按下之后从画布外边移动到画布内部

  canvas.addEventListener("mouseenter",function(e){
      if(mouseStatus){
         startX=e.offsetX
         startY=e.offsetY
      }
    })

第三步 重置鼠标坐标的情况 第一种情况:鼠标弹起

 document.addEventListener("mouseup",function () {
      mouseStatus=false
      startX=-1
      startY=-1
    })

第二种情况:鼠标离开画布

canvas.addEventListener("mouseleave",function(){
    startX=-1
    startY=-1
})

第四-六步 监测鼠标在canvas画布上的移动事件,然后开始绘图

  canvas.addEventListener("mousemove",function (e) {
      // 鼠标还是按下状态
      if (mouseStatus) {
        if(startX>=0&&startY>=0){
           startDraw(e.offsetX,e.offsetY)
        }
      }
    })
     function startDraw(x,y) {
      ctx.beginPath()
      ctx.moveTo(startX,startY)
      ctx.lineCap="round"
      ctx.lineWidth="8"
      ctx.lineTo(x,y)
      startX=x
      startY=y
      ctx.stroke()
      ctx.closePath()
    }

清除画布内容

使用canvasclearReact方法将将画布内容置空

document.querySelector(".clear").addEventListener("click",clear)
  function clear(){
    let w=canvas.width
    let h=canvas.height
   ctx.closePath()
   ctx.clearRect(0,0,w,h)
   ctx.beginPath()
  }

原生js实现的源码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box{
      width: 600px;
      border: 1px solid #000;
    }
    #canvas:active {
      cursor: pointer;
    }
    .img-box{
      background-color: skyblue;
      width: 200px;
      height: 200px;
    }
    .img-box img{
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<body>
  <div class="box">
      <canvas id="canvas" width="600" height="400"></canvas>  
  </div>
  <button class="btn">保存</button>
  <button class="clear">清除</button>
  <div class="img-box">
  </div>
  <script>
    /** @type {HTMLCanvasElement}**/
    const canvas=document.querySelector("#canvas")
    const ctx=canvas.getContext("2d")
    let startX=-1
    let startY=-1
    let mouseStatus=false
    document.addEventListener("mousedown",function (e) {
        mouseStatus=true
    })
    document.addEventListener("mouseup",function () {
      mouseStatus=false
      startX=-1
      startY=-1
    })
    canvas.addEventListener("mousedown",function(e){
         startX=e.offsetX
         startY=e.offsetY
    })
    canvas.addEventListener("mousemove",function (e) {
      // 鼠标还是按下状态
      if (mouseStatus) {
        if(startX>=0&&startY>=0){
           startDraw(e.offsetX,e.offsetY)
        }
      }
    })
    canvas.addEventListener("mouseleave",function(){
        startX=-1
        startY=-1
    })
    canvas.addEventListener("mouseenter",function(e){
      if(mouseStatus){
         startX=e.offsetX
         startY=e.offsetY
      }
    })
    function startDraw(x,y) {
      ctx.beginPath()
      ctx.moveTo(startX,startY)
      ctx.lineCap="round"
      ctx.lineWidth="8"
      ctx.lineTo(x,y)
      startX=x
      startY=y
      ctx.stroke()
    }

    // 这里主要是用于将画出的canvas图形转化为图片,然后展示在页面上
    let image=new Image();
    document.querySelector(".btn").addEventListener("click",function () {
      image.src
      url=canvas.toDataURL({ format: 'image/png',quality: 1,width: 600,height: 400 });
      image.src=url
      console.log("url==",url)
      document.querySelector(".img-box").appendChild(image)
    })
    document.body.append(image)

  // 清空画布
  document.querySelector(".clear").addEventListener("click",clear)
  function clear(){
    let w=canvas.width
    let h=canvas.height
   ctx.closePath()
   ctx.clearRect(0,0,w,h)
   ctx.beginPath()
  }

  </script>
</body>

</html>

vue实现canvas画布手动签名

vue实现的话原理与上述方法一致只是写法不同,这个是我在实际开发中使用的主要在el-dialog弹窗中实现,源码如下

<template>
  <el-dialog
    title=""
    :visible.sync="dialogVisible"
    width="800px"
    @close="handleClose"
    append-to-body
    center
  >
    <div class="tip">请将鼠标移到黑色方框内书写</div>
    <div class="content">
      <canvas
        ref="signal"
        class="canvas"
        @mousedown="canvasMouseDown"
        @mousemove="canvasMouseMove"
        @mouseenter="canvasMouseEnter"
        @mouseleave="canvasMouseLeave"
      ></canvas>
    </div>
    <span
      slot="footer"
      class="dialog-footer"
    >
      <el-button @click="clearSign">清 除</el-button>
      <el-button
        type="primary"
        @click="createSignatrueImg"
      >保 存</el-button>
    </span>
  </el-dialog>
</template>

<script>
export default {
  data() {
    return {
      dialogVisible: false, //弹窗显示
      startAxis: {
        //鼠标开始画图的开始坐标,也即是canvas的开始画图坐标
        x: -1,
        y: -1,
      },
      context: null, //画布上下文
      mouseStatus: false, //鼠标是不是按下状态,是:true,否false
      documentMouseDown: null, //鼠标按下事件,主要用于关闭弹窗的时候移除全局事件
      documentMouseUp: null, //鼠标弹起事件
    };
  },
  methods: {
    /**
     * 打开弹窗的方法,使用this.$refs.[弹窗组件ref].show()打开
     * **/
    show() {
      this.dialogVisible=true
      // 鼠标按下
      this.documentMouseDown = () => {
        this.mouseStatus = true;
      };
      document.addEventListener("mousedown", this.documentMouseDown);
      // 鼠标抬起
      this.documentMouseUp = () => {
        this.mouseStatus = false;
        this.startAxis.x = -1;
        this.startAxis.y = -1;
      };
      document.addEventListener("mouseup", this.documentMouseUp);
      // 这一步必须要,否则获取不到元素
      this.$nextTick(() => {
        const canvas = this.$refs.signal;
        this.context = canvas.getContext("2d");
        let content = document.querySelector(".content");
        canvas.width = content.clientWidth;
        canvas.height = content.clientHeight;
      });
    },
    /**
     * 生成签名图片
     * **/
    createSignatrueImg() {
      // 截取图片的类型
      let mime = "image/png";
      const canvas = this.$refs.signal;
      // 判断canvas内容区是否为空
      let blank = document.createElement("canvas");
      blank.width = canvas.width;
      blank.height = canvas.height;
      this.dialogVisible = false;
      if (canvas.toDataURL() == blank.toDataURL()) {
        return;
      }
      let base64 = canvas.toDataURL({
        format: mime,
        quality: 1,
        width: 60,
        height: 30,
      });
      // 先将base64转为blob,再转为file
      let arr = base64.split(",");
      //  使用 atob() 函数将 base64 字符串解码为二进制字符串
      let bstr = atob(arr[1]);
      let n = bstr.length;
      // 使用 Uint8Array 构造函数将二进制字符串转换为字节数组
      let u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      //将二进制字符串数组转为Blob
      let blob = new Blob([u8arr], { type: mime });
      // 将Blob转为file
      let file = new File([blob], "signature.png");
      this.$emit("update", file);
    },
    /**
     * 清除画布
     * **/
    clearSign() {
      const canvas = this.$refs.signal;
      let w = canvas.width;
      let h = canvas.height;
      this.context.closePath();
      this.context.clearRect(0, 0, w, h);
      this.context.beginPath();
    },
    /**
     * canvas的鼠标按下事件
     * **/
    canvasMouseDown(e) {
      this.startAxis.x = e.offsetX;
      this.startAxis.y = e.offsetY;
    },
    /**
     * canvas的鼠标移动事件
     * **/
    canvasMouseMove(e) {
      // 鼠标是按下状态
      if (this.mouseStatus) {
        if (this.startAxis.x >= 0 && this.startAxis.y >= 0) {
          this.startDraw(e.offsetX, e.offsetY);
        }
      }
    },
    /**
     * canvas画图写字
     * **/
    startDraw(x, y) {
      this.context.beginPath();
      this.context.moveTo(this.startAxis.x, this.startAxis.y);
      this.context.lineCap = "round";
      this.context.lineWidth = "5";
      this.context.lineTo(x, y);
      this.startAxis.x = x;
      this.startAxis.y = y;
      this.context.stroke();
      this.context.closePath();
    },
    /**
     * canvas的鼠标进入事件
     * **/
    canvasMouseEnter(e) {
      if (this.mouseStatus) {
        this.startAxis.x = e.offsetX;
        this.startAxis.y = e.offsetY;
      }
    },
    /**
     * canvas的鼠标离开事件
     * **/
    canvasMouseLeave() {
      this.startAxis.x = -1;
      this.startAxis.y = -1;
    },
    /**
     * 弹窗关闭事件,清除全局监听事件
     * **/
    handleClose() {
      document.removeEventListener("mousedown", this.documentMouseDown);
      document.removeEventListener("mouseup", this.documentMouseUp);
    },
  },
};
</script>

<style lang="scss" scoped>
.tip {
  margin-bottom: 10px;
}
.content {
  width: 600px;
  height: 300px;
  width: 100%;
  border: 1px solid #000;
  border-radius: 4px;
  padding: 0;
  .canvas:hover {
    cursor: pointer;
  }
}
</style>