移动端 canvas 横屏 签名 图片旋转

该文章介绍了一个基于Vue.js的前端组件,用于实现电子签名功能。组件通过原生canvas元素捕捉触控事件,记录并绘制用户签名轨迹。用户可以进行签名、清除和保存操作,最终将签名保存为base64格式的图片。代码示例展示了如何初始化canvas、处理触摸事件以及转换和旋转签名图像。
摘要由CSDN通过智能技术生成

学习与 前端实现电子签名

是通过原生canvas实现,我这篇是对其进行了改造,记录一下

  • 效果图

  • 组件 components/esign.vue
<template>
  <div style="width: 100%;height: 100%;">
    <div class="drawing-board">
      <canvas
        id="canvas"
        @touchstart="touchStart"
        @touchmove="touchMove"
        @touchend="touchEnd"
      ></canvas>
    </div>
    <!-- <div class="tool-bar">
        <van-button @click="reset()" size="small" type="default">清除</van-button>
        <van-button @click="save()" size="small" type="primary">确定</van-button>
      </div> -->
  </div>
</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;

export default {
  mounted() {
    this.drawingBoardInit();
  },
  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编码
            resolve(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'));
        };
      });
    },
  },
};
</script>


<style scoped lang="stylus">
.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>
  • 使用组件
import vueEsign from '@/components/esign';


components: {
    vueEsign
}


<div class="esign">
  <vue-esign ref="esign" />
</div>

// 因为canvas 根据外部宽高设置的
.esign {
    height 100vh
}


清除
this.$refs.esign.reset();


获取图片base64
this.$refs.esign
        .save()
        .then((res) => {
          this.resultImg = res;
        })
        .catch((err) => {
          // 画布没有签字时会执行这里
          this.$dialog.alert({
            message: err,
          });
        });

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值