uni-app 签名组件

1. 背景

在使用uni-app搬砖过程中,有一个需要签名的需求,于是有了以下代码。

2. 代码实现

<template>
  <uni-popup ref="popup" background-color="#fff" :mask-click="false">
    <view class="popup-content">
      <!-- 签名横屏 -->
      <view class="signa">
        <view class="box">
          <canvas
            class="canvas"
            disable-scroll="true"
            :style="{ width: width, height: height }"
            canvas-id="designature"
            @touchstart="starts"
            @touchmove="moves"
          ></canvas>
          <view class="bottomBox u-flex">
            <view class="tip">请你在此手动签写自己的签章,签写操作完毕点击保存按钮</view>
            <view class="btn" style="margin-top: 18vw" @click="cancel">取消</view>
            <view class="btn" style="margin-top: 8vw" @click="clear">清除</view>
            <view class="btn blue" @click="save">提交</view>
          </view>
        </view>
      </view>
    </view>
  </uni-popup>
</template>

<script>

export default {
  name: 'FSignature',
  model: { prop: 'value', event: 'change' },
  props: {
    value: { type: Boolean },
  },
  watch: {
    value(val) {
      this.$refs.popup[val ? 'open' : 'close']()
    },
  },
  data() {
    return {
      resultUrl: '',
      dom: null,
      line: [],
      width: '0px',
      height: '0px',
      radius: 0,
      isSign: false, //是否签名
    }
  },
  created() {
    uni.getSystemInfo({
      success: (res) => {
        this.width = res.windowWidth - 56 + 'px'
        this.height = res.windowHeight - 154 + 'px'
      },
    })
    this.dom = uni.createCanvasContext('designature', this)
  },
  methods: {
    cancel() {
      this.$emit('change', false)
      this.isSign = false
    },
    distance(a, b) {
      let x = b.x - a.x
      let y = b.y - a.y
      return Math.sqrt(x * x + y * y)
    },
    starts(e) {
      this.line.push({
        points: [
          {
            time: new Date().getTime(),
            x: e.touches[0].x,
            y: e.touches[0].y,
            dis: 0,
          },
        ],
      })
      let currentPoint = {
        x: e.touches[0].x,
        y: e.touches[0].y,
      }
      this.currentPoint = currentPoint
      this.drawer(this.line[this.line.length - 1])
      this.isSign = true
    },
    moves(e) {
      let point = {
        x: e.touches[0].x,
        y: e.touches[0].y,
      }
      ;(this.lastPoint = this.currentPoint), (this.currentPoint = point)
      this.line[this.line.length - 1].points.push({
        time: new Date().getTime(),
        x: e.touches[0].x,
        y: e.touches[0].y,
        dis: this.distance(this.currentPoint, this.lastPoint),
      })
      this.drawer(this.line[this.line.length - 1])
    },
    drawer(item) {
      let x1,
        x2,
        y1,
        y2,
        cx,
        cy,
        t = 0.5,
        x,
        y
      var time = 0
      if (item.points.length > 2) {
        let lines = item.points[item.points.length - 3]
        let line = item.points[item.points.length - 2]
        let end = item.points[item.points.length - 1]
        x = line.x
        y = line.y
        x1 = lines.x
        y1 = lines.y
        x2 = end.x
        y2 = end.y
        var dis = 0
        time = line.time - lines.time + (end.time - line.time)
        dis = line.dis + lines.dis + end.dis
        var dom = this.dom
        var or = Math.min((time / dis) * this.linePressure + this.lineMin, this.lineMax)
        cx = (x - Math.pow(1 - t, 2) * x1 - Math.pow(t, 2) * x2) / (2 * t * (1 - t))
        cy = (y - Math.pow(1 - t, 2) * y1 - Math.pow(t, 2) * y2) / (2 * t * (1 - t))
        dom.setLineCap('round')
        dom.beginPath()
        dom.setStrokeStyle('black')
        dom.setLineWidth(5)
        dom.moveTo(x1, y1)
        dom.quadraticCurveTo(cx, cy, x2, y2)

        dom.stroke()
        dom.draw(true)
      }
    },
    clear() {
      this.isSign = false
      this.dom.clearRect(0, 0, 1000, 1000)
      this.dom.draw()
    },
    save() {
      var t = this
      if (!this.isSign) {
        uni.showToast({ title: '请签名', icon: 'none' })
        return
      }
      uni.canvasToTempFilePath(
        {
          canvasId: 'designature',
          fileType: 'png',
          quality: 1, //图片质量
          success: (res) => {
            this.rotateBase64Img(res.tempFilePath, 270, this.success)
          },
          fail(e) {
            console.log(e)
          },
        },
        this,
      )
    },
    // 旋转成功后的回调
    success(sign) {
      this.$emit('getSign', sign)
      setTimeout(() => {
        this.$emit('change', false)
      }, 500)
    },
    // 当前为横屏签字,需旋转图片
    rotateBase64Img(src, edg, callback) {
      var canvas = document.createElement('canvas')
      var ctx = canvas.getContext('2d')
      var imgW //图片宽度
      var imgH //图片高度
      var size //canvas初始大小
      if (edg % 90 != 0) {
        console.error('旋转角度必须是90的倍数!')
        throw '旋转角度必须是90的倍数!'
      }
      edg < 0 && (edg = (edg % 360) + 360)
      const quadrant = (edg / 90) % 4 //旋转象限
      const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 } //裁剪坐标
      var image = new Image()
      image.crossOrigin = 'anonymous'
      image.src = src
      image.onload = function () {
        imgW = image.width
        imgH = image.height
        size = imgW > imgH ? imgW : imgH
        canvas.width = size * 2
        canvas.height = size * 2
        switch (quadrant) {
          case 0:
            cutCoor.sx = size
            cutCoor.sy = size
            cutCoor.ex = size + imgW
            cutCoor.ey = size + imgH
            break
          case 1:
            cutCoor.sx = size - imgH
            cutCoor.sy = size
            cutCoor.ex = size
            cutCoor.ey = size + imgW
            break
          case 2:
            cutCoor.sx = size - imgW
            cutCoor.sy = size - imgH
            cutCoor.ex = size
            cutCoor.ey = size
            break
          case 3:
            cutCoor.sx = size
            cutCoor.sy = size - imgW
            cutCoor.ex = size + imgH
            cutCoor.ey = size + imgW
            break
        }
        ctx.translate(size, size)
        ctx.rotate((edg * Math.PI) / 180)
        ctx.drawImage(image, 0, 0)
        var imgData = ctx.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey)
        if (quadrant % 2 == 0) {
          canvas.width = imgW
          canvas.height = imgH
        } else {
          canvas.width = imgH
          canvas.height = imgW
        }
        ctx.putImageData(imgData, 0, 0)
        callback(canvas.toDataURL())
      }
    },
  },
}
</script>

<style scoped lang="scss">
.signa {
  overflow: hidden;
  background-color: #fff;
  height: 100vh;
  width: 100vw;
  padding: 20rpx 30rpx;
  .box {
    background-color: #fff;
    border-radius: 20rpx;
    padding: 24rpx;
    box-shadow: 0 6rpx 42rpx rgba(0, 0, 0, 0.05);
  }
  .canvas {
    background-color: #e6e6e6;
    position: relative;
    z-index: 9999;
  }
  .bottomBox {
    flex-direction: column;
    justify-content: center;
    width: 200rpx;
    height: 35vw;
    transform: rotate(90deg) translateY(-390rpx);
    .tip {
      font-size: 22rpx;
      color: #ff6f77;
    }
    .btn {
      width: 100%;
      height: 74rpx;
      background: #f5f5f5;
      border-radius: 40rpx;
      margin-top: 14rpx;
      text-align: center;
      line-height: 74rpx;
      color: #333333;
      font-size: 26rpx;
      &.blue {
        background: #284cf4;
        color: #fff;
      }
    }
  }
}
</style>

3. 使用方法

<FSignature v-model="isShowSign" @getSign="getSign" />

4. 实现效果

在这里插入图片描述

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值