vue canvas手绘签名

需求

公司业务想要在手机端h5搞个签名需求。签名好像在哪里见到过;好像钉钉上请假时有个签名可以仿照写一个

思路想法

  1. 既然是绘制 那想到的是用 canvas 或者svg 应该都可以
  2. 既然canvas 或者svg 那用哪个比较好呢
  3. Canvas提供的功能更原始,适合像素处理,动态渲染和大数据量绘制;SVG功能更完善,适合静态图片展示,高保真文档查看和打印的应用场景;
    1. Canvas 像素图在元素特别多的情况 1000+
      1. 性能高,可以自己控制绘制过程,还能使用 WebGL
      2. 可控性高,像素级控制
      3. 内存占用恒定,就是像素点个数
    2. SVG 矢量图:
      1. 不失真,放大缩小图像都很清晰
      2. 学习成本低,也是一种 DOM 结构
      3. 使用方便,设计软件可以直接导出

优点都是网上找的,具体的差别小弟不是很清楚,为啥用canvas来制作,主要还是用的多,svg我比较少用

遇到问题:

1、使用canvas绘制签名在使用情况下感觉不是很流畅,有点卡顿的感觉

2、使用canvas绘制玩后不知各位打扰会不会觉得绘制区域有些模糊,不是很清晰

对于第一个问题我是通过对比 钉钉的签名来的,发现钉钉如德福一样纵享丝滑,自己的就有点捞了,于是在网上找,发现大部分都是用的canvas来绘制,我在万军丛中找到了那么几个使用第三方包vue-esign,使用他们的示例,好像有内味了,看了一下他们源码,发现该插件内部也是用的canvas,既然都是那为何还要用插件呢,直接看着源码改造出来,最终有那丝滑般的感觉的,问题就出在,点击绘制和移除绘制的过程中。但是哪个模糊的问题没有解决。待后续处理技术有限,期待大佬们的交流

上图

具体代码如下

img那个橡皮擦,是拿的阿里图标库的我这就不给出了

touchend一般是手机移动端设备的触碰事件,如果是移动设备可以把click事件去除,不然点击会触发两次事件,所以酌情选择

<template>
  <div class="myBrush-container" ref="myBrush">
    <div class="brush_btn">
      <div class="resign" @click="closeBrush" @touchend="closeBrush">取消</div>
      <span class="title">手写签名</span>
      <div class="confirm" :disabled="!this.hasDrew" @click="generate" @touchend="generate">完成</div>
    </div>
    <div class="brush_content">
      <canvas ref="canvas" @mousedown="mouseDown" @mousemove="mouseMove" @mouseup="mouseUp"
              @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"></canvas>
      <img @click="resetDraw" @touchend="resetDraw" class="content_img" src="../assets/clear.png" alt="">
    </div>
  </div>
</template>

<script>
export default {
  name: "myBrush",
  mounted() {
    this.$_init()
    // 在画板以外松开鼠标后冻结画笔
    document.onmouseup = () => {
      this.isDrawing = false
    }
  },
  props: {
    //线宽
    lineWidth: {
      type: Number,
      default: 8
    },
    //线的颜色
    lineColor: {
      type: String,
      default: '#000000'
    },
    //背景颜色
    bgColor: {
      type: String,
      default: '#FFF'
    },
    //是否裁剪 在画布设定尺寸基础上裁掉四周空白部分
    isCrop: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      ctx: {},
      hasDrew: false,
      resultImg: '',
      points: [],
      canvasTxt: null,
      startX: 0,
      startY: 0,
      isDrawing: false,//是否绘制
      sratio: 1,//宽比率
    }
  },
  computed: {
    myBg() {
      return this.bgColor ? this.bgColor : 'rgba(255, 255, 255, 0)'
    }
  },
  watch: {
    'myBg': function (newVal) {
      this.$refs.canvas.style.background = newVal
    }
  },
  beforeMount() {
    window.addEventListener('resize', this.$_resizeHandler)
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.$_resizeHandler)
  },
methods: {
    $_init() {
      const canvas = this.$refs.canvas
      canvas.height = this.$refs.myBrush.offsetHeight;
      canvas.width = this.$refs.myBrush.offsetWidth;
      canvas.style.background = this.myBg
      const realW = parseFloat(window.getComputedStyle(canvas).width)
      this.canvasTxt = canvas.getContext('2d')
      this.canvasTxt.scale(1 * this.sratio, 1 * this.sratio)
      this.sratio = realW / this.$refs.myBrush.offsetWidth
      this.canvasTxt.scale(1 / this.sratio, 1 / this.sratio)
    },
    // pc
    mouseDown(e) {
      e = e || event
      e.preventDefault()
      this.isDrawing = true
      this.hasDrew = true
      let obj = {
        x: e.offsetX,
        y: e.offsetY
      }
      this.drawStart(obj)
    },
    mouseMove(e) {
      e = e || event
      e.preventDefault()
      if (this.isDrawing) {
        let obj = {
          x: e.offsetX,
          y: e.offsetY
        }
        this.drawMove(obj)
      }
    },
    mouseUp(e) {
      e = e || event
      e.preventDefault()
      let obj = {
        x: e.offsetX,
        y: e.offsetY
      }
      this.drawEnd(obj)
      this.isDrawing = false
    },
    // mobile
    touchStart(e) {
      e = e || event
      e.preventDefault()
      this.hasDrew = true
      if (e.touches.length === 1) {
        let obj = {
          x: e.targetTouches[0].clientX - this.$refs.canvas.getBoundingClientRect().left,
          y: e.targetTouches[0].clientY - this.$refs.canvas.getBoundingClientRect().top
        }
        this.drawStart(obj)
      }
    },
    touchMove(e) {
      e = e || event
      e.preventDefault()
      if (e.touches.length === 1) {
        let obj = {
          x: e.targetTouches[0].clientX - this.$refs.canvas.getBoundingClientRect().left,
          y: e.targetTouches[0].clientY - this.$refs.canvas.getBoundingClientRect().top
        }
        this.drawMove(obj)
      }
    },
    touchEnd(e) {
      e = e || event
      e.preventDefault()
      if (e.touches.length === 1) {
        let obj = {
          x: e.targetTouches[0].clientX - this.$refs.canvas.getBoundingClientRect().left,
          y: e.targetTouches[0].clientY - this.$refs.canvas.getBoundingClientRect().top
        }
        this.drawEnd(obj)
      }
    },
    // 绘制
    drawStart(obj) {
      this.startX = obj.x
      this.startY = obj.y
      this.canvasTxt.beginPath()
      this.canvasTxt.moveTo(this.startX, this.startY)
      this.canvasTxt.lineTo(obj.x, obj.y)
      this.canvasTxt.lineCap = 'round'
      this.canvasTxt.lineJoin = 'round'
      this.canvasTxt.lineWidth = this.lineWidth * this.sratio
      this.canvasTxt.stroke()
      this.canvasTxt.closePath()
      this.points.push(obj)
    },
/**
     * 移动
     */
    drawMove(obj) {
      this.canvasTxt.beginPath()
      this.canvasTxt.moveTo(this.startX, this.startY)
      this.canvasTxt.lineTo(obj.x, obj.y)
      this.canvasTxt.strokeStyle = this.lineColor
      this.canvasTxt.lineWidth = this.lineWidth * this.sratio
      this.canvasTxt.lineCap = 'round'
      this.canvasTxt.lineJoin = 'round'
      this.canvasTxt.stroke()
      this.canvasTxt.closePath()
      this.startY = obj.y
      this.startX = obj.x
      this.points.push(obj)
    },
    /**
     *结束绘制
     */
    drawEnd(obj) {
      this.canvasTxt.beginPath()
      this.canvasTxt.moveTo(this.startX, this.startY)
      this.canvasTxt.lineTo(obj.x, obj.y)
      this.canvasTxt.lineCap = 'round'
      this.canvasTxt.lineJoin = 'round'
      this.canvasTxt.stroke()
      this.canvasTxt.closePath()
      this.points.push(obj)
      this.points.push({x: -1, y: -1})
    },
    // 操作
    async generate() {
      if (!this.hasDrew) {
        this.$message.warning(`Warning: Not Signned!`)
        return
      }
      var resImgData = this.canvasTxt.getImageData(0, 0, this.$refs.canvas.width, this.$refs.canvas.height)
      this.canvasTxt.globalCompositeOperation = "destination-over"
      this.canvasTxt.fillStyle = this.myBg
      this.canvasTxt.fillRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height)
      this.resultImg = this.$refs.canvas.toDataURL('image/png');
      this.$refs.canvas.toBlob(async (blobObj) => {
        const file1 = new File([blobObj], "signature.png", {
          type: blobObj.type,
          lastModified: Date.now(),
        });
        console.log(file1);
      })
      var resultImg = this.resultImg
      this.canvasTxt.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height)
      this.canvasTxt.putImageData(resImgData, 0, 0)
      this.canvasTxt.globalCompositeOperation = "source-over"
      if (this.isCrop) {
        const crop_area = this.getCropArea(resImgData.data)
        var crop_canvas = document.createElement('canvas')
        const crop_ctx = crop_canvas.getContext('2d')
        crop_canvas.width = crop_area[2] - crop_area[0]
        crop_canvas.height = crop_area[3] - crop_area[1]
        const crop_imgData = this.canvasTxt.getImageData(...crop_area)
        crop_ctx.globalCompositeOperation = "destination-over"
        crop_ctx.putImageData(crop_imgData, 0, 0)
        crop_ctx.fillStyle = this.myBg
        crop_ctx.fillRect(0, 0, crop_canvas.width, crop_canvas.height)
        resultImg = crop_canvas.toDataURL()
        crop_canvas = null
      }
      console.log(resultImg)

    },
    /**
     * 重绘
     */
    resetDraw() {
      this.canvasTxt.clearRect(
          0,
          0,
          this.$refs.canvas.width,
          this.$refs.canvas.height
      )
      this.$emit('update:bgColor', '')
      this.$refs.canvas.style.background = 'rgba(255, 255, 255, 0)'
      this.points = []
      this.hasDrew = false
      this.resultImg = ''
    },
 /**
     * 关闭画板
     */
    closeBrush() {
      this.resetDraw();
      this.$emit("closeBrush")
    },
    /**
     * 修剪区域
     * @param imgData
     * @returns {(number|number)[]}
     */
    getCropArea(imgData) {
      var topX = this.$refs.canvas.width;
      var btmX = 0;
      var topY = this.$refs.canvas.height;
      var btnY = 0
      for (var i = 0; i < this.$refs.canvas.width; i++) {
        for (var j = 0; j < this.$refs.canvas.height; j++) {
          var pos = (i + this.$refs.canvas.width * j) * 4
          if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {
            btnY = Math.max(j, btnY)
            btmX = Math.max(i, btmX)
            topY = Math.min(j, topY)
            topX = Math.min(i, topX)
          }
        }
      }
      topX++
      btmX++
      topY++
      btnY++
      return [topX, topY, btmX, btnY]
    }
}
}
</script>

<style scoped lang="scss">
.myBrush-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  height: 50%;
  position: fixed;
  bottom: 0;
  background-color: #fff;
  border-radius: 10px 10px 0 0;
  padding: 1%;
  box-sizing: border-box;
  z-index: 2011;

  .brush_btn {
    height: 5vh;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 2%;
    border-bottom: 1px solid #dcdfe6;
    box-sizing: border-box;

    .confirm, .resign {
      height: 80%;
      box-sizing: border-box;
      line-height: 4vh;
      color: #fff;
      border-radius: 18px;
    }

    .title {
      font-weight: bold;
    }

    .resign {
      background: #fff;
      color: #606266;
      width: 10%;
    }

    .confirm {
      background: #409EFF;
      width: 18%;
    }
  }

  .brush_content {
    width: 100%;
    height: calc(100% - 5vh);
    padding: 4%;
    box-sizing: border-box;

    canvas {
      border: 1px dashed #dcdfe6;
      height: 100%;
      width: 100%;
      box-sizing: border-box;
    }

    .content_img {
      position: absolute;
      bottom: 6%;
      right: 10%;
      width: 24px;
      height: 24px;
    }
  }
}


</style>

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值