This is the 5th day of my participation in the August More Text Challenge


background

The small program service requires users to sign an order to confirm the order. After confirming the order, a picture is displayed. Take stock of the implementation process and problems encountered. The realization effect is shown as follows:

Code implementation

1. Requirements analysis

Look at this demand, first disassemble the demand and sort out the implementation process. Referring to the figure above, it can be seen that the implementation steps are generally as follows:

  1. Page landscape, page layout, initializationCanvasThe container;
  2. CanvasDraw operation reminders and other text;
  3. touchstart()Event creates the starting point of the path, storing its coordinates in the path coordinate array.
  4. touchmove()Event to continuously store path coordinates into the array.
  5. touchend()Event empties the array for the next drawing;

2. Hand operation

1. Landscape, layout, and initializationCanvasThe container

First is the first step, because it is in the small program, the mobile phone needs to be horizontal screen to obtain a larger space. “pageOrientation” can be set in the small program: “Landscape” is the most convenient. If the canvas is rotated, the resulting image is also rotated and needs to be rotated back. There is also a small Canvas component (the full code is attached at the end of this article, only the JS part is described here).

  initCanvas() {
    let {
      width,
      height
    } = this.data
    width = wx.getSystemInfoSync().windowWidth
    height = wx.getSystemInfoSync().windowHeight
    this.data.ctx = wx.createCanvasContext('canvas'.this)
    this.setData({
      width,
      height
    })
    this.clearCanvas()
  },
Copy the code

Because CTX objects need to be shared among multiple methods, they can be defined in data or external variables.

2.CanvasDraw text such as operation reminders

You can set your setTextAlign(‘ Center ‘) and fillText to the position of the x axis at half the width.

  clearCanvas() {
    this.data.drawCount = 0
    this.data.ctx.setTextBaseline('top')
    this.data.ctx.setTextAlign('center')
    this.data.ctx.setFontSize(20)
    this.data.ctx.setFillStyle('# 616165');
    this.data.ctx.fillText("Please sign in the gray area.".this.data.width / 2.30)
    this.data.ctx.draw(false)},Copy the code
3.touchstart()The event creates the starting point of the path, storing its coordinates in the path coordinate array

The warning text drawn in the previous step is similar to the input placeholder and needs to be cleared when the Canvas is triggered. Therefore, call the draw(false) event if it is triggered for the first time. False means to overwrite the previous drawing, and true means to retain the previous drawing

  catchtouchstart(e) {
    if (this.data.drawCount == 0) {
      this.data.ctx.draw(false)}this.data.drawCount++
    this.data.points.push(e.changedTouches[0])},Copy the code
  • Touches: A list of all touches on the current screen;
  • ChangedTouches: List of touches that trigger the current event

Example: Two fingers trigger the event at the same time, then there are two elements in both attributes; When two fingers trigger an event, the first finger doesn’t leave the screen, the second finger triggers only one changedTouches element, which represents the touch point of the currently triggered event, and two Touches, which represent the touch points of the current two fingers.

4.touchmove()Event to continuously store path coordinates into the array

Because the path needs to be plotted in real time as the gesture moves, the Draw () event is executed inside the TouchMove () event. The setShadow() event sets a shadow on the track to make the handwriting look smoother.

  catchtouchmove(e) {
    if (e.touches.length > 1) {
      return
    }
    this.data.points.push(e.changedTouches[0])
    let points = this.data.points
    for (let i in points) {
      if (i == 0) {
        this.data.ctx.moveTo(points[0].clientX, points[0].clientY)
      } else {
        this.data.ctx.lineTo(points[i].clientX, points[i].clientY)
      }
    }
    this.data.ctx.setStrokeStyle('# 000000');
    this.data.ctx.setLineWidth(3);
    this.data.ctx.setShadow(0.0.0.5.'# 000000')
    this.data.ctx.setLineCap('round');
    this.data.ctx.setLineJoin('round');
    this.data.ctx.stroke()
    this.data.ctx.draw(true)},Copy the code
5.touchend()The event empties the array for the next drawing
  catchtouchend(e) {
    this.data.points = []
  },
Copy the code

3. Code test optimization

When the code is run in real time, it is found that the longer the trajectory is drawn, the more it starts to generate stutter:

Because all trace points are drawn each time, more trace points will consume more performance. Therefore, another idea is to connect two points to form a line segment. When the touchStart () event is triggered, it will be the starting point of the first line segment, and when the touchmove() event is triggered, it will be the end point of the first line segment. After the line segment is drawn, this point will be set as the starting point of the next line segment. This obviously solves the caton problem.

  catchtouchstart(e) {
    if (this.data.drawCount == 0) {
      this.data.ctx.draw(false)}this.data.drawCount++
    this.data.ctx.moveTo(e.changedTouches[0].clientX, e.changedTouches[0].clientY)
  },
  catchtouchmove(e) {
    if (this.data.drawState == "stop") return
    this.data.drawState = "ing"
    if (e.touches.length > 1) {
      return
    }
    this.data.ctx.setStrokeStyle('# 000000');
    this.data.ctx.setLineWidth(3);
    this.data.ctx.setShadow(0.0.0.5.'# 000000')
    this.data.ctx.setLineCap('round');
    this.data.ctx.setLineJoin('round');
    this.data.ctx.lineTo(e.changedTouches[0].clientX, e.changedTouches[0].clientY)
    this.data.ctx.stroke()
    this.data.ctx.draw(true)
    this.data.ctx.moveTo(e.changedTouches[0].clientX, e.changedTouches[0].clientY)
  },
Copy the code

Modified effect:

At the end

The solution was to compare the coordinates of changedTouches[0] with those of the last one created, and assume that the same finger touches [0] were created when they were smaller than a certain value. However, the scheme was rejected by the product, and the probability of scene occurrence is low. It is only necessary to judge the single touch before drawing.

The complete code is attached at the end:

<canvas canvas-id="canvas" style="width:{{width+'px'}}; height:{{height+'px'}}" catchtouchstart="catchtouchstart" catchtouchmove="catchtouchmove" catchtouchend="catchtouchend"></canvas>
<view class="btn-reset" catchtap="clearCanvas">redraw</view>
<view class="btn-ok" catchtap="canvasToImg">confirm</view>
Copy the code
Page({
  data: {
    ctx: null.width: null.height: null.drawCount: 0.drawState: "init"
  },
  onShow: function () {
    this.initCanvas()
  },
  initCanvas() {
    let {
      width,
      height
    } = this.data
    width = wx.getSystemInfoSync().windowWidth
    height = wx.getSystemInfoSync().windowHeight
    console.log(wx.getSystemInfoSync())
    this.data.ctx = wx.createCanvasContext('canvas')
    this.setData({
      width,
      height
    })
    this.clearCanvas()
  },
  clearCanvas() {
    this.data.drawCount = 0
    this.data.drawState = "ing"
    this.data.ctx.setTextBaseline('top')
    this.data.ctx.setTextAlign('center')
    this.data.ctx.setFontSize(20)
    this.data.ctx.setFillStyle('# 616165');
    this.data.ctx.fillText("Please sign in the gray area.".this.data.width / 2.30)
    this.data.ctx.draw(false)},catchtouchstart(e) {
    if (this.data.drawCount == 0) {
      this.data.ctx.draw(false)}this.data.drawCount++
    this.data.ctx.moveTo(e.changedTouches[0].clientX, e.changedTouches[0].clientY)
  },
  catchtouchmove(e) {
    if (this.data.drawState == "stop") return
    this.data.drawState = "ing"
    if (e.touches.length > 1) {
      return
    }
    this.data.ctx.setStrokeStyle('# 000000');
    this.data.ctx.setLineWidth(3);
    this.data.ctx.setShadow(0.0.0.5.'# 000000')
    this.data.ctx.setLineCap('round');
    this.data.ctx.setLineJoin('round');
    this.data.ctx.lineTo(e.changedTouches[0].clientX, e.changedTouches[0].clientY)
    this.data.ctx.stroke()
    this.data.ctx.draw(true)
    this.data.ctx.moveTo(e.changedTouches[0].clientX, e.changedTouches[0].clientY)
  },
  canvasToImg() {
    if (this.data.drawState == "init") return
    this.data.drawState = "stop"
    wx.canvasToTempFilePath({
      canvasId: 'canvas'.success: res= > {
        console.log(res.tempFilePath)
        // ...
        // Upload server
        wx.navigateTo({
          url: '/pages/showImg/showImg? src=' + res.tempFilePath,
        })
      }
    })
  }
})
Copy the code
page{
  position: relative;
  background-color: #f2f2f2;
  width: 100%;
  height: 100%;
}
canvas{
  width: 100%;
  height: 100vh;
}
.btn-reset{
  width: 100rpx;
  position: absolute;
  bottom: 10rpx;
  right: 160rpx;
  padding: 8rpx;
  text-align: center;
  border: 1rpx solid #4965B3;
  color: #4965B3;
  font-size: 18rpx;
  border-radius: 8rpx;
  box-sizing: border-box;
}
.btn-ok{
  width: 100rpx;
  position: absolute;
  bottom: 10rpx;
  right: 30rpx;
  padding: 8rpx;
  text-align: center;
  background-color: #4965B3;
  border: 1rpx solid #4965B3;
  color: #fff;
  font-size: 18rpx;
  border-radius: 8rpx;
  box-sizing: border-box;
}
Copy the code