vue项目中使用的移动端的签名组件,纯 js 写的

发布时间 2023-12-22 15:13:50作者: smil、梵音
<template>
  <section>
    <div class="sign-wrap">
      <div class="main">
        <div class="box" style="width: 100%;height: 100%">
          <!-- <vue-esign ref="esign" :width="600"  :height='1375' :isCrop="isCrop" :lineWidth="lineWidth" :lineColor="lineColor" :bgColor.sync="bgColor" /> -->
          <div class="drawing-board">
            <canvas id="canvas" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"></canvas>
          </div>
        </div>

      </div>
      <div class="dialog-header">签名确认</div>
      <div class="dialog-footer btns">
        <div class="btn confirm-btn  confirm-btn1" @click="reset">重置</div>
        <div class="btn confirm-btn " @click="save">确认</div>
      </div>
    </div>
  </section>
</template>

<script>
  const $ = (name) => document.querySelector(name);
  // 配置内容
  const config = {
    width: 0, // 宽度
    height: 0, // 高度
    lineWidth: 5, // 线宽
    strokeStyle: '#000000', // 线条颜色
    lineCap: 'round', // 设置线条两端圆角
    lineJoin: 'round', // 线条交汇处圆角
  };

  // 偏移量
  const client = {
    offsetX: 0,
    offsetY: 0,
  };

  let canvas;
  let ctx;

  //  import { XButton } from "vux";
  export default {
    //  components: { XButton },
    data() {
      return {
        lineWidth: 6,
        lineColor: "#000000",
        bgColor: "#ccc",
        resultImg: "", //base64结果数据
        isCrop: true, //是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
      };
    },
    mounted() {
      setTimeout(() => {
        if($('.drawing-board')){
          this.drawingBoardInit()
        }
      }, 600)

    },


    methods: {
      drawingBoardInit() {

        const {
          width,
          height,
          left,
          top,
        } = $('.drawing-board').getBoundingClientRect();
        config.width = width;
        config.height = height;
        client.offsetX = left;
        client.offsetY = top;
        // canvas 实例
        canvas = $('#canvas');
        // 设置宽高
        canvas.width = config.width;
        canvas.height = config.height;
        // 设置边框
        //   canvas.style.border = '1px solid #000';
        // 创建上下文
        ctx = canvas.getContext('2d');
        // 设置填充背景色
        ctx.fillStyle = 'transparent';
        // 绘制填充矩形
        ctx.fillRect(
          0, // x 轴起始绘制位置
          0, // y 轴起始绘制位置
          config.width, // 宽度
          config.height, // 高度
        );
      },
      // 鼠标按下
      touchStart(event) {
        event.preventDefault();
        // 获取偏移量及坐标
        const {
          clientX,
          clientY
        } = event.changedTouches[0];
        // 清除以上一次 beginPath 之后的所有路径,进行绘制
        ctx.beginPath();
        // 根据配置文件设置相应配置
        ctx.lineWidth = config.lineWidth;
        ctx.strokeStyle = config.strokeStyle;
        ctx.lineCap = config.lineCap;
        ctx.lineJoin = config.lineJoin;
        // 设置画线起始点位(减去 左边、上方的偏移量很关键)
        ctx.moveTo(clientX - client.offsetX, clientY - client.offsetY);
      },
      // 绘制
      touchMove(event) {

        // 获取当前坐标点位;
        const {
          clientX,
          clientY
        } = event.changedTouches[0];
        // 根据坐标点位移动添加线条(减去 左边、上方的偏移量很关键)
        ctx.lineTo(clientX - client.offsetX, clientY - client.offsetY);
        // 绘制
        ctx.stroke();
      },
      // 结束绘制
      touchEnd() {

        // 结束绘制
        ctx.closePath();
        // 移除鼠标移动或手势移动监听器
        window.removeEventListener('mousemove', this.draw);
      },
      // 清除
      reset() {
        // 清空当前画布上的所有绘制内容
        ctx.clearRect(0, 0, config.width, config.height);
      },
      // 将画布内容保存为图片
      save() {
        return new Promise((resolve, reject) => {
          if (!this.isCanvasBlank(canvas)) {
            this.rotateBase64(canvas.toDataURL('image/png')).then((img) => {
              const imgBase64 = img;
              // console.log(imgBase64, 'imgBase64-->>'); // base64编码
              this.$emit("handleImg", imgBase64);
            });
          } else {
            const err = '请签名';
            reject(err);
          }
        });
        // 将canvas上的内容转成blob流
        //   canvas.toBlob((blob) => {
        //     console.log(blob, 'blob-->>'); // 文件二进制流
        //     // 获取当前时间,用来当做文件名
        //     const date = new Date().getTime();
        //     // 创建一个 a 标签
        //     const link = document.createElement('a');
        //     // 设置 a 标签的下载文件名
        //     link.download = `${date}.png`;
        //     // 设置 a 标签的跳转路径为 文件流地址
        //     link.href = URL.createObjectURL(blob);
        //     // 手动触发 a 标签的点击事件
        //     link.click();
        //     // 移除 a 标签
        //     link.remove();
        //   });
      },
      // 判断canvas对象是否空
      isCanvasBlank(canvas) {
        const blank = document.createElement('canvas'); // 系统获取一个空canvas对象
        blank.width = config.width;
        blank.height = config.height;
        return canvas.toDataURL() === blank.toDataURL(); // 比较值相等则为空
      },
      // 将base64图片旋转90度以后上传
      rotateBase64(imgBase64) {
        return new Promise((resolve) => {
          const imgView = new Image();
          imgView.src = imgBase64;
          const canvas = document.createElement('canvas');
          const context = canvas.getContext('2d');
          const cutCoor = {
            sx: 0,
            sy: 0,
            ex: 0,
            ey: 0,
          };
          // 裁剪坐标
          imgView.onload = () => {
            const imgW = imgView.width;
            const imgH = imgView.height;
            const size = imgH;
            //   常量大小 = imgH;
            canvas.width = size * 2;
            canvas.height = size * 2;
            cutCoor.sx = size;
            cutCoor.sy = size - imgW;
            cutCoor.ex = size + imgH;
            cutCoor.ey = size + imgW;
            context.translate(size, size);
            context.rotate((Math.PI / 2) * 3);
            context.drawImage(imgView, 0, 0);
            const imgData = context.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
            canvas.width = imgH;
            canvas.height = imgW;
            context.putImageData(imgData, 0, 0);
            resolve(canvas.toDataURL('image/png'));
          };
        });
      },
      // // 初始化方法
      // init() {
      //   this.$nextTick(() => {
      //     this.$refs.esign.reset();
      //   });
      // },
      // // 清空画板
      // handleReset() {
      //   this.$refs.esign.reset();
      // },
      // // 生成照片
      // handleGenerate() {
      //   // 生成图片
      //   // 可选配置参数 ,在未设置format或quality属性时可在生成图片时配置 例如: {format:'image/jpeg', quality: 0.5}
      //   // this.$refs.esign.generate({format:'image/jpeg', quality: 0.5})
      //   this.$refs.esign.generate().then((base64) => {
      //     this.resultImg = base64; //默认生成的是base64形式的图片
      //     // 将生成的base64格式的图片传给父组件
      //     this.$emit("handleImg", base64);
      //     //   如果需要下载
      //     //   const a = document.createElement("a");
      //     //   a.href = res;
      //     //   a.download = "签名.png";
      //     //   a.click();
      //     //   a.remove();
      //   })
      //     .catch((err) => {
      //       this.toast_warn(err); // 画布没有签字时会执行这里 'Not Signned'
      //     });
      // },
    },
  };
</script>

<style lang="less">
  section {
    /* height: calc(100% - 44px) */
    height:100%;
  }

  .sign-wrap {
    height: 100%;
    position: relative;

    .main {
      background-color: #ffffff;
      padding: 20px 50px 20px 77px;
      height: 100%;
    }

    .box {
      margin: 0 auto;
      background: #ecf0fa;
      border-radius: 14px;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }

    .dialog-footer {
      display: flex;
      align-items: center;
      justify-content: space-around;
      width: 100%;

      .btn {
        font-size: 16px;
        margin-left: 14px;
        color: #666;
        padding: 14px 34px;
        text-align: center;
        box-sizing: border-box;
        background-color: #f8f8f8;
        border-radius: 4px;
        border-radius: 12px;
      }

      .confirm-btn {
        background-color: #117af1;
        color: #fff;
      }

      .confirm-btn1 {
        background-color: #fff;
        border: 1px solid #117af1;
        color: #117af1;
      }
    }
  }

  .dialog-header {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-box-align: center;
    -ms-flex-align: center;
    align-items: center;
    -ms-flex-pack: distribute;
    justify-content: space-around;
    width: 100%;
    position: absolute;
    -webkit-transform: rotate(90deg);
    top: 9%;
    transform: rotate(90deg);
    right: -3%;
    font-size: 19px;
    width: auto !important;
    font-weight: bold;
  }

  .btns {
    position: absolute;
    -webkit-transform: rotate(90deg);
    bottom: 18%;
    transform: rotate(90deg);
    left: -20%;
    width: auto !important;
  }

  .drawing-board {
    width: 100%;
    height: calc(100%);
    /* border-bottom: 1px solid #ccc; */
    box-sizing: border-box;
  }

  .tool-bar {
    width: 100%;
    height: 40px;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    position: absolute;
    top: 46%;
    left: -4rem;
    transform: rotate(90deg);

    .van-button {
      flex: 1
    }
  }
</style>