[uniapp] Wechat applet canvas signature rotation to generate pictures

In the process of using uniapp to develop WeChat applets, I encountered the need for approval and signature. There are still many signed codes on the Internet. There are many masters in the plug-in market. , Helplessly consulted me, the details are how to rotate the drawn canvas to an angle and then generate a picture.

If it’s just for the conclusion, you don’t need to look at the following: Rotate the picture and let the backend do it, which is the fastest and most convenient (after all, java has ready-made tools). If there is such an actual business, leave it to them. Don’t be embarrassed. And the backend can do more, such as not allowing empty panels to be saved through the color difference tool .

However, to apply Qian Lao's spirit of "what can be done at the back end, why can't the front end be done? Is the front end shorter than others?" And realize to increase our own professional self-confidence, the front end is no longer the previous "cut picture boy".

So how to solve it? And see the following ideas.

First describe the specific background:

        1. To approve the signature, the user needs to "write" on a blank canvas by touching the screen with his finger and sliding, and save it as a picture after completion to achieve the purpose of electronic signature.

        2. Place a canvas tag on the page, set a sliding event, call uni.canvasToTempFilePath to generate a temporary image address when you click Save, and then upload it to the background through uni.uploadFile, and then generate a picture of the user's handwritten content and save it on the server .

but! The general signature needs to be handwritten by the user on the horizontal screen, but for the mobile phone, it is actually written vertically, and the saved picture is also in the shape of the mobile phone (vertical long type). If it is displayed on the PC side, it needs to be rotated with css , but if there is a need to generate pdf in the background, it is not easy to rotate the picture. In order to once and for all, it has to be controlled from the source of generation.

Solution 1: Rotate the canvas in the saved event

The context of the canvas is canvasContext.rotate that provides the rotation function, but the rotation has no effect in the actual process, and if it succeeds, it is not good for the user experience, so give up.

Solution 2: Add another "hidden" canvas. After the drawing is completed, dynamically rotate and copy the content of the signature canvas to the "hidden" canvas in the save event, and then generate a picture of the latter content and upload it . The curve fulfills our needs.

This idea is also provided by the official. For details, see  How to rotate the canvas after it is drawn? | WeChat Open Community

speak in code

<template>
  <view>
    <!-- 自定义导航栏 -->
    <NaviBar title="签署" :autoBack="true" />
    <view class="wrapper">
      <view class="handBtn">
        <button @click="retDraw" class="delBtn">清除</button>
        <button @click="saveCanvasAsImg" class="saveBtn">取消</button>
        <button @click="subCanvas" class="subBtn">确认</button>
      </view>
      <view class="handCenter">
        <canvas class="handWriting" :disable-scroll="true" @touchstart="uploadScaleStart" @touchmove="uploadScaleMove" canvas-id="handWriting" />
        <!--用于旋转图片的canvas容器-->
        <canvas style="position: absolute" :style="{ width: cavWidth, height: cavWidth1 }" canvas-id="handWriting2"></canvas>
      </view>
    </view>
  </view>
</template>

<script>
import { BASE_URL } from '@/common/config.js'
import { getUToken } from '@/store'
import { storage } from '@/api/api'
import { replaceCurrentSignApi } from '@/api/flow.js'
export default {
  name: 'Signature',
  data() {
    return {
      canvasName: 'handWriting',
      ctx: '',
      startX: null,
      startY: null,
      canvasWidth: 0,
      canvasHeight: 0,
      selectColor: 'black',
      lineColor: '#1A1A1A', // 颜色
      canvas: null,
      cavWidth: 2000,
      cavWidth1: 2000,
      lineSize: 5 // 笔记倍数
    }
  },
  onLoad() {
    this.ctx = uni.createCanvasContext('handWriting', this)
    this.$nextTick(() => {
      uni
        .createSelectorQuery()
        .select('.handCenter')
        .boundingClientRect((rect) => {
          this.canvasWidth = rect.width
          this.canvasHeight = rect.height
          /* 将canvas背景设置为 白底,不设置  导出的canvas的背景为透明 */
          this.setCanvasBg('#fff')
        })
        .exec()
    })
  },
  methods: {
    // 笔迹开始
    uploadScaleStart(e) {
      this.startX = e.changedTouches[0].x
      this.startY = e.changedTouches[0].y
      //设置画笔参数
      //画笔颜色
      this.ctx.setStrokeStyle(this.lineColor)
      //设置线条粗细
      this.ctx.setLineWidth(this.lineSize)
      //设置线条的结束端点样式
      this.ctx.setLineCap('round') //'butt'、'round'、'square'
      //开始画笔
      this.ctx.beginPath()
    },
    // 笔迹移动
    uploadScaleMove(e) {
      //取点
      let temX = e.changedTouches[0].x
      let temY = e.changedTouches[0].y
      //画线条
      this.ctx.moveTo(this.startX, this.startY)
      this.ctx.lineTo(temX, temY)
      this.ctx.stroke()
      this.startX = temX
      this.startY = temY
      this.ctx.draw(true)
    },
    /**
     * 重写
     */
    retDraw() {
      this.ctx.clearRect(0, 0, 700, 730)
      this.ctx.draw()
      //设置canvas背景
      this.setCanvasBg('#fff')
    },
    /**
     * @param {Object} str
     * @param {Object} color
     * 选择颜色
     */
    selectColorEvent(str, color) {
      this.selectColor = str
      this.lineColor = color
    },
    // 确认
    subCanvas() {
      // uni.canvasToTempFilePath({
      //   canvasId: 'handWriting',
      //   fileType: 'png',
      //   quality: 1, //图片质量
      //   success(res) {
      //     // console.log(res.tempFilePath, 'canvas生成图片地址');
      //     const { uToken } = getUToken()
      //     uni.uploadFile({
      //       url: `${BASE_URL}${storage}/upload`,
      //       header: {
      //         'staff-token': uToken
      //       },
      //       filePath: res.tempFilePath,
      //       name: 'file',
      //       formData: {
      //         plateform: '',
      //         fileType: 'image'
      //       },
      //       success: ({ statusCode, data, errMsg }) => {
      //         const parseData = JSON.parse(data)
      //         if (statusCode == 200 && parseData.code == 100) {
      //           replaceCurrentSignApi({
      //             fileId: parseData.data.fileId
      //           }).then(([err, res]) => {
      //             if (err) {
      //               uni.showToast('保存签名失败')
      //               return
      //             }
      //             // 返回上一层并传参
      //             let pages = getCurrentPages() // 获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。
      //             let prevPage = pages[pages.length - 2] //上一页页面实例
      //             prevPage.$vm.otherFun(parseData) // 给上一页绑定方法otherFun,传参地址id
      //             uni.navigateBack()
      //           })
      //         } else {
      //           uni.showToast(errMsg)
      //         }
      //       }
      //     })
      //   }
      // })
      const _this = this

      uni.canvasToTempFilePath({
        canvasId: 'handWriting',
        fileType: 'png',
        quality: 1, //图片质量
        success(res) {
          console.log(res.tempFilePath, 'canvas生成图片地址')
          wx.getImageInfo({
            // 获取图片的信息
            src: res.tempFilePath,
            success: (res1) => {
              console.log(res1)
              // 将canvas1的内容复制到canvas2中
              let canvasContext = wx.createCanvasContext('handWriting2')
              let rate = res1.height / res1.width
              let width = 300 / rate
              let height = 300
              _this.cavWidth = 300 / rate
              _this.cavWidth1 = 300
              canvasContext.translate(height / 2, width / 2)
              canvasContext.rotate((270 * Math.PI) / 180)
              canvasContext.drawImage(res.tempFilePath, -width / 2, -height / 2, width, height)
              canvasContext.draw(false, () => {
                // 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中
                wx.canvasToTempFilePath({
                  // 把当前画布指定区域的内容导出生成指定大小的图片。在 draw() 回调里调用该方法才能保证图片导出成功。
                  canvasId: 'handWriting2',
                  fileType: 'png',
                  quality: 1, //图片质量
                  success(res2) {
                    // 调用uni.uploadFile上传图片即可
                    console.log(res2)
                  }
                })
              })
            }
          })
        }
      })
      // wx.createSelectorQuery()
      //   .select('#handWriting')
      //   .fields({ node: true, size: true })
      //   .exec((res) => {
      //     console.log(res);
      //     _this.canvas = res[0].node
      //     // const rotateCtx = rotateCanvas.getContext('2d')
      //   })
    },
    //旋转图片,生成新canvas实例
    rotate(cb) {
      const that = this
      wx.createSelectorQuery()
        .select('#handWriting2')
        .fields({ node: true, size: true })
        .exec((res) => {
          const rotateCanvas = res[0].node
          const rotateCtx = rotateCanvas.getContext('2d')
          //this.ctxW-->所绘制canvas的width
          //this.ctxH -->所绘制canvas的height
          rotateCanvas.width = this.ctxH
          rotateCanvas.height = this.ctxW
          wx.canvasToTempFilePath({
            canvas: that.canvas,
            success(res) {
              const img = rotateCanvas.createImage()
              img.src = res.tempFilePath
              img.onload = function () {
                rotateCtx.translate(rotateCanvas.width / 2, rotateCanvas.height / 2)
                rotateCtx.rotate((270 * Math.PI) / 180)
                rotateCtx.drawImage(img, -rotateCanvas.height / 2, -rotateCanvas.width / 2)
                rotateCtx.scale(that.pixelRatio, that.pixelRatio)
                cb(rotateCanvas)
              }
            },
            fail(err) {
              console.log(err)
            }
          })
        })
    },
    //取消
    saveCanvasAsImg() {
      this.retDraw()
      uni.navigateBack()
    },
    //设置canvas背景色  不设置  导出的canvas的背景为透明
    //@params:字符串  color
    setCanvasBg(color) {
      /* 将canvas背景设置为 白底,不设置  导出的canvas的背景为透明 */
      //rect() 参数说明  矩形路径左上角的横坐标,左上角的纵坐标, 矩形路径的宽度, 矩形路径的高度
      //这里是 canvasHeight - 4 是因为下边盖住边框了,所以手动减了写
      this.ctx.rect(0, 0, this.canvasWidth, this.canvasHeight - 4)
      // ctx.setFillStyle('red')
      this.ctx.setFillStyle(color)
      this.ctx.fill() //设置填充
      this.ctx.draw() //开画
    },
    toJSON() {}
  }
}
</script>

<style>
page {
  background: #fbfbfb;
  height: auto;
  overflow: hidden;
}

.wrapper {
  position: relative;
  width: 100%;
  height: 85vh;
  margin: 20rpx 0;
  overflow: auto;
  display: flex;
  align-content: center;
  flex-direction: row;
  justify-content: center;
  font-size: 28rpx;
}

.handWriting {
  background: #fff;
  width: 100%;
  height: 85vh;
}

.handCenter {
  border-left: 2rpx solid #e9e9e9;
  flex: 5;
  overflow: hidden;
  box-sizing: border-box;
}

.handBtn button {
  font-size: 28rpx;
}

.handBtn {
  height: 85vh;
  display: inline-flex;
  flex-direction: column;
  justify-content: space-between;
  align-content: space-between;
  flex: 1;
}

.delBtn {
  width: 200rpx;
  position: absolute;
  bottom: 350rpx;
  left: -35rpx;
  transform: rotate(90deg);
  color: #666;
}

.subBtn {
  width: 200rpx;
  position: absolute;
  bottom: 52rpx;
  left: -35rpx;
  display: inline-flex;
  transform: rotate(90deg);
  background: #29cea0;
  color: #fff;
  margin-bottom: 60rpx;
  text-align: center;
  justify-content: center;
}

/*Peach - 新增 - 保存*/

.saveBtn {
  width: 200rpx;
  position: absolute;
  bottom: 590rpx;
  left: -35rpx;
  transform: rotate(90deg);
  color: #666;
}
</style>

Code highlights:

 

 Let's see the specific effect:

Just draw one, just click to save, and then go to synchronize the second canvas. At this time, there is no confirmation.

 

 Click OK, you can see that the content is generated.

 In this way, the front-end can realize the rotating upload of the canvas.

Follow up:

In the actual test process, there will be compatibility issues with the size of the canvas. Specifically, the background of the canvas set in the code is white. On iPhones with certain screen widths (the aspect ratio is larger than iPhone7), the generated pictures may be There is a situation where the vertical column area on the right (actually the bottom area of ​​the picture if it is not rotated) is transparent, please use a picture to indicate it.

 It may be due to the ctx.clearRect, ctx.rect filling area colleagues are hard-coded, or the filling height is inconsistent with the canvas height? , or the problem of the canvas itself, it is recommended not to dynamically obtain the width and height of the canvas to dynamically fill the color of the specified range, but be more violent and directly fill the 2000 size area, similar to this.ctx.clearRect(0, 0, 2000, 2000 ), this.ctx.rect(0, 0, 2000, 2000), save time and effort haha.

Reference materials (material 2 only provides ideas, not realized):

How to rotate the canvas after drawing? | WeChat Open Community

WeChat applet picture upload after rotation

Guess you like

Origin blog.csdn.net/rrrrroy_Ha/article/details/126864111