Nail / wechat applet canvas handwritten signature, signature rotation

###Foreword
Recently, the nail applet developed by the company requires handwritten signature. I wrote one. Let's see the effect first. Wechat applet can also be used.

####The first step is page layout
I found an example of handwritten signature on the Internet, copied and modified it, and posted the code directly:

//Layout code
<view class="wrapper">
  //Signature display area
  <view class="handCenter">
    <view class="ats">
      //Signature drawing canvas (the first canvas will be described later)
      <canvas class="handWriting" disable-scroll="true" onTouchStart="uploadScaleStart" onTouchMove="uploadScaleMove" onTouchEnd="uploadScaleEnd" 
        onTap="mouseDown" id="handWriting">
      </canvas>
      //Signature rotation canvas (the second canvas will be described later)
      <canvas class="handWriting2" id="canvas"></canvas>
    </view>
    //Operation button
    <view class="handBtn">
      <button onTap="subCanvas" class="subBtn">complete</button>
      <button onTap="retDraw" class="delBtn">Reset</button>
    </view>
  </view>
  //title
  <view class="handRight">
    <text class="handTitle">autograph</text>
  </view>
</view>

####The second step is css style (only the important ones are pasted completely at the bottom)
Because the applet does not support screen rotation (it does not seem to support it at present), the layout control will rotate 90 °, which is the following property

transform: rotate(90deg);

The second canvas style should be hidden under the first one and not displayed. It is only used to generate rotating pictures:

.ats{
   position: relative;
}
//First canvas
.handWriting {
  position: relative;
  background: #fff;
  width: 98%;
  z-index: 99;
  height: 83vh;
  border: 4rpx dashed #e9e9e9;
}
//Second canvas
.handWriting2{
  position: absolute;
  z-index: 9;
  width: 600rpx;
  height: 300rpx;
  top: 0;
  left: 0;
  /* transform: rotate(90deg); */
  background-color: #FFF;
}

####Step 3: JS code (only say the important ones and paste them completely at the bottom)
I won't say how to draw the handwriting. I also copied it. I said the problems I encountered and solved.
When we finish writing, we need to save. We all know that we need to use the method of toTempFilePath (converting Canvas content into temporary pictures, which is different from wechat applet methods), but the saved pictures are vertical, like this:

What we need to do is to rotate. We can't rotate the first canvas. The experience is too bad, so the second hidden canvas is useful. Method:

  drowImage(path) {
    //This path is the temporary path of the first canvas. Bring it here
    var canvasContext = my.createCanvasContext('canvas')//Unique identification of the second canvas
    canvasContext.scale(0.5, 0.5);//Double the size of the first canvas, because my first one is relatively large and can be adjusted by myself. It is too large and the display is incomplete
    canvasContext.translate(0, 300)//This is the origin offset (the height of the second canvas is 300 and the width is 600)
    canvasContext.rotate(-90 * Math.PI / 180)//Rotate 90 ° counterclockwise to get the horizontal signature
    canvasContext.drawImage(path, 0, 0, 300, 600)//Draw the first one on the second
    canvasContext.draw(true)//Note that it takes time to draw this place. You need to delay obtaining the temporary picture below, otherwise you may get blank because the draw() method is not completed
    setTimeout(() => {
      //Convert the second Canvas content to a temporary picture
      canvasContext.toTempFilePath({
        success(res) {
          console.log("AAAAAFFFFF" + res.filePath)
          //Upload pictures
          dd.uploadFile({
            url: 'https://www.aa./ API / qiniupload / upload ', / / your address
            filePath: res.filePath,
            fileType: 'image',
            fileName: 'file',
            header: {
              'content-type': 'multipart/form-data'
            },
            success: (res) => {
              // res.data is the relevant data returned by your backend
              const data = JSON.parse(res.data)
              var urls =  data.data
              var pages = getCurrentPages(); // Get page stack
              var prevPage = pages[pages.length - 2]; // Previous page
              console.log("AAAAAFFFFF" + urls)
              md5.gone();
              prevPage.setData({
                qian: urls // Send the picture address back to the previous page
              })
              my.navigateBack({//return
                delta: 1
              })
            },
            fial: (res) => {
              console.log("AAAAAFFFFF" + res)
            }
          })
        }
      })
    }, 1000)
  },

Then I got this picture:

####Step 4: summary
One of the most troublesome problems is to rotate the picture. I don't know if there is any other better method (maybe I'm too delicious). I can solve this method. The second problem is that when drawing () redraws, I always get blank!!!!, Finally, I found that I need to delay the setTimeout before taking it. As mentioned above, finally paste all the codes and copy them directly. The address of the uploaded picture needs to be changed.
####JS all codes

Page({

	/**
	 * Initial data of the page
	 */
  data: {
    canvasName: 'handWriting',
    ctx: '',
    canvasWidth: 0,
    canvasHeight: 0,
    transparent: 1, // transparency
    selectColor: 'black',
    lineColor: '#1a1a ', / / color
    lineSize: 1.5,  // Note multiple
    lineMin: 0.5,   // Minimum stroke radius
    lineMax: 4,     // Maximum stroke radius
    pressure: 1,     // Default pressure
    smoothness: 60,  //Smoothness, the speed is calculated with a distance of 60
    currentPoint: {},
    currentLine: [],  // Current line
    firstTouch: true, // First trigger
    radius: 1, //Radius of circle
    cutArea: { top: 0, right: 0, bottom: 0, left: 0 }, //Clipping Region 
    bethelPoint: [],  //Save Bezier points generated by all lines;
    lastPoint: 0,
    chirography: [], //Handwriting
    currentChirography: {}, //Current handwriting
    linePrack: [] //Scribe track to generate the actual points of the line
  },

  /*======All custom functions======*/

  // Handwriting start
  uploadScaleStart(e) {
    if (e.type != 'touchStart') return false;
    let ctx = this.data.ctx;
    ctx.setFillStyle(this.data.lineColor);  // Initial line set color
    ctx.setGlobalAlpha(this.data.transparent);  // Set translucency
    let currentPoint = {
      x: e.touches[0].x,
      y: e.touches[0].y
    }
    let currentLine = this.data.currentLine;
    currentLine.unshift({
      time: new Date().getTime(),
      dis: 0,
      x: currentPoint.x,
      y: currentPoint.y
    })
    this.setData({
      currentPoint,
      // currentLine
    })
    if (this.data.firstTouch) {
      this.setData({
        cutArea: { top: currentPoint.y, right: currentPoint.x, bottom: currentPoint.y, left: currentPoint.x },
        firstTouch: false
      })
    }
    this.pointToLine(currentLine);
  },
  // Handwriting movement
  uploadScaleMove(e) {
    if (e.type != 'touchMove') return false;
    if (e.cancelable) {
      // Determines whether the default behavior has been disabled
      if (!e.defaultPrevented) {
        e.preventDefault();
      }
    }
    let point = {
      x: e.touches[0].x,
      y: e.touches[0].y
    }

    //Test clipping
    if (point.y < this.data.cutArea.top) {
      this.data.cutArea.top = point.y;
    }
    if (point.y < 0) this.data.cutArea.top = 0;

    if (point.x > this.data.cutArea.right) {
      this.data.cutArea.right = point.x;
    }
    if (this.data.canvasWidth - point.x <= 0) {
      this.data.cutArea.right = this.data.canvasWidth;
    }
    if (point.y > this.data.cutArea.bottom) {
      this.data.cutArea.bottom = point.y;
    }
    if (this.data.canvasHeight - point.y <= 0) {
      this.data.cutArea.bottom = this.data.canvasHeight;
    }
    if (point.x < this.data.cutArea.left) {
      this.data.cutArea.left = point.x;
    }
    if (point.x < 0) this.data.cutArea.left = 0;

    this.setData({
      lastPoint: this.data.currentPoint,
      currentPoint: point
    })
    let currentLine = this.data.currentLine
    currentLine.unshift({
      time: new Date().getTime(),
      dis: this.distance(this.data.currentPoint, this.data.lastPoint),
      x: point.x,
      y: point.y
    })
    // this.setData({
    //   currentLine
    // })
    this.pointToLine(currentLine);
  },
  // End of handwriting
  uploadScaleEnd(e) {
    if (e.type != 'touchEnd') return 0;
    let point = {
      x: e.changedTouches[0].x,
      y: e.changedTouches[0].y
    }
    this.setData({
      lastPoint: this.data.currentPoint,
      currentPoint: point
    })
    let currentLine = this.data.currentLine
    currentLine.unshift({
      time: new Date().getTime(),
      dis: this.distance(this.data.currentPoint, this.data.lastPoint),
      x: point.x,
      y: point.y
    })
    // this.setData({
    //   currentLine
    // })
    if (currentLine.length > 2) {
      var info = (currentLine[0].time - currentLine[currentLine.length - 1].time) / currentLine.length;
      //$("#info").text(info.toFixed(2));
    }
    //At the end of a stroke, save the coordinate points of the handwriting, clear the current handwriting
    //Add judgment whether it is in the handwriting area;
    this.pointToLine(currentLine);
    var currentChirography = {
      lineSize: this.data.lineSize,
      lineColor: this.data.lineColor
    };
    var chirography = this.data.chirography
    chirography.unshift(currentChirography);
    this.setData({
      chirography
    })
    var linePrack = this.data.linePrack
    linePrack.unshift(this.data.currentLine);
    this.setData({
      linePrack,
      currentLine: []
    })
  },

  retDraw() {
    this.data.ctx.clearRect(0, 0, 700, 730)
    this.data.ctx.draw();

    //Set canvas background
    this.setCanvasBg("#fff");
  },

  //Draw a line between two points; The parameter is: line, and the two nearest starting points will be drawn;
  pointToLine(line) {
    this.calcBethelLine(line);
    return;
  },
  //How to calculate interpolation;
  calcBethelLine(line) {
    if (line.length <= 1) {
      line[0].r = this.data.radius;
      return;
    }
    let x0, x1, x2, y0, y1, y2, r0, r1, r2, len, lastRadius, dis = 0, time = 0, curveValue = 0.5;
    if (line.length <= 2) {
      x0 = line[1].x
      y0 = line[1].y
      x2 = line[1].x + (line[0].x - line[1].x) * curveValue;
      y2 = line[1].y + (line[0].y - line[1].y) * curveValue;
      //x2 = line[1].x;
      //y2 = line[1].y;
      x1 = x0 + (x2 - x0) * curveValue;
      y1 = y0 + (y2 - y0) * curveValue;;

    } else {
      x0 = line[2].x + (line[1].x - line[2].x) * curveValue;
      y0 = line[2].y + (line[1].y - line[2].y) * curveValue;
      x1 = line[1].x;
      y1 = line[1].y;
      x2 = x1 + (line[0].x - x1) * curveValue;
      y2 = y1 + (line[0].y - y1) * curveValue;
    }
    //From the calculation formula, the three points are (x0,y0),(x1,y1),(x2,y2); (x1,y1) this is the control point, which will not fall on the curve; In fact, this point will also be the actual point obtained by handwriting, but it falls on the curve
    len = this.distance({ x: x2, y: y2 }, { x: x0, y: y0 });
    lastRadius = this.data.radius;
    for (let n = 0; n < line.length - 1; n++) {
      dis += line[n].dis;
      time += line[n].time - line[n + 1].time;
      if (dis > this.data.smoothness) break;
    }
    this.setData({
      radius: Math.min(time / len * this.data.pressure + this.data.lineMin, this.data.lineMax) * this.data.lineSize
    });
    line[0].r = this.data.radius;
    //Calculate the handwriting radius;
    if (line.length <= 2) {
      r0 = (lastRadius + this.data.radius) / 2;
      r1 = r0;
      r2 = r1;
      //return;
    } else {
      r0 = (line[2].r + line[1].r) / 2;
      r1 = line[1].r;
      r2 = (line[1].r + line[0].r) / 2;
    }
    let n = 5;
    let point = [];
    for (let i = 0; i < n; i++) {
      let t = i / (n - 1);
      let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2;
      let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2;
      let r = lastRadius + (this.data.radius - lastRadius) / n * i;
      point.push({ x: x, y: y, r: r });
      if (point.length == 3) {
        let a = this.ctaCalc(point[0].x, point[0].y, point[0].r, point[1].x, point[1].y, point[1].r, point[2].x, point[2].y, point[2].r);
        a[0].color = this.data.lineColor;
        // let bethelPoint = this.data.bethelPoint;
        // console.log(a)
        // console.log(this.data.bethelPoint)
        // bethelPoint = bethelPoint.push(a);
        this.bethelDraw(a, 1);
        point = [{ x: x, y: y, r: r }];
      }
    }
    this.setData({
      currentLine: line
    })
  },
  //Find the distance between two points
  distance(a, b) {
    let x = b.x - a.x;
    let y = b.y - a.y;
    return Math.sqrt(x * x + y * y);
  },
  ctaCalc(x0, y0, r0, x1, y1, r1, x2, y2, r2) {
    let a = [], vx01, vy01, norm, n_x0, n_y0, vx21, vy21, n_x2, n_y2;
    vx01 = x1 - x0;
    vy01 = y1 - y0;
    norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2;
    vx01 = vx01 / norm * r0;
    vy01 = vy01 / norm * r0;
    n_x0 = vy01;
    n_y0 = -vx01;
    vx21 = x1 - x2;
    vy21 = y1 - y2;
    norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2;
    vx21 = vx21 / norm * r2;
    vy21 = vy21 / norm * r2;
    n_x2 = -vy21;
    n_y2 = vx21;
    a.push({ mx: x0 + n_x0, my: y0 + n_y0, color: "#1A1A1A" });
    a.push({ c1x: x1 + n_x0, c1y: y1 + n_y0, c2x: x1 + n_x2, c2y: y1 + n_y2, ex: x2 + n_x2, ey: y2 + n_y2 });
    a.push({ c1x: x2 + n_x2 - vx21, c1y: y2 + n_y2 - vy21, c2x: x2 - n_x2 - vx21, c2y: y2 - n_y2 - vy21, ex: x2 - n_x2, ey: y2 - n_y2 });
    a.push({ c1x: x1 - n_x2, c1y: y1 - n_y2, c2x: x1 - n_x0, c2y: y1 - n_y0, ex: x0 - n_x0, ey: y0 - n_y0 });
    a.push({ c1x: x0 - n_x0 - vx01, c1y: y0 - n_y0 - vy01, c2x: x0 + n_x0 - vx01, c2y: y0 + n_y0 - vy01, ex: x0 + n_x0, ey: y0 + n_y0 });
    a[0].mx = a[0].mx.toFixed(1);
    a[0].mx = parseFloat(a[0].mx);
    a[0].my = a[0].my.toFixed(1);
    a[0].my = parseFloat(a[0].my);
    for (let i = 1; i < a.length; i++) {
      a[i].c1x = a[i].c1x.toFixed(1);
      a[i].c1x = parseFloat(a[i].c1x);
      a[i].c1y = a[i].c1y.toFixed(1);
      a[i].c1y = parseFloat(a[i].c1y);
      a[i].c2x = a[i].c2x.toFixed(1);
      a[i].c2x = parseFloat(a[i].c2x);
      a[i].c2y = a[i].c2y.toFixed(1);
      a[i].c2y = parseFloat(a[i].c2y);
      a[i].ex = a[i].ex.toFixed(1);
      a[i].ex = parseFloat(a[i].ex);
      a[i].ey = a[i].ey.toFixed(1);
      a[i].ey = parseFloat(a[i].ey);
    }
    return a;
  },
  bethelDraw(point, is_fill, color) {
    let ctx = this.data.ctx;
    ctx.beginPath();
    ctx.moveTo(point[0].mx, point[0].my);
    if (undefined != color) {
      ctx.setFillStyle(color);
      ctx.setStrokeStyle(color);
    } else {
      ctx.setFillStyle(point[0].color);
      ctx.setStrokeStyle(point[0].color);
    }
    for (let i = 1; i < point.length; i++) {
      ctx.bezierCurveTo(point[i].c1x, point[i].c1y, point[i].c2x, point[i].c2y, point[i].ex, point[i].ey);
    }
    ctx.stroke();
    if (undefined != is_fill) {
      ctx.fill(); //Fill graphics (the graphics drawn later will overwrite the previous graphics, and pay attention to the order when drawing)
    }
    ctx.draw(true)
  },

  //complete
  subCanvas() {
    if (this.data.linePrack.length == 0) {
      util.showToast("No signature")
    } else {
      this.uploadCanvasImg();
    }

  },

  //upload
  uploadCanvasImg() {
    var that = this;
    // This is a prompt, dd.showloading ({content: Message});
    md5.loading('Signature generation in progress...');
    that.data.ctx.toTempFilePath({
      success(res) {

        that.drowImage(res.filePath);

      }

    })


  },

  //Set the canvas background color. Do not set the background of the exported canvas to be transparent 
  //@params: String color
  setCanvasBg(color) {
    /* Set the canvas background to white background, and do not set the exported canvas background to transparent */
    //The rect() parameter describes the abscissa of the upper left corner of the rectangular path, the ordinate of the upper left corner, the width of the rectangular path, and the height of the rectangular path
    //Here is canvasHeight - 4, because the lower edge covers the border, so it is written down manually
    this.data.ctx.rect(0, 0, this.data.canvasWidth, this.data.canvasHeight - 4);
    // ctx.setFillStyle('red')
    this.data.ctx.setFillStyle(color)
    this.data.ctx.fill()  //Set fill
    this.data.ctx.draw()	//Open painting


  },




  drowImage(path) {
    //This path is the temporary path of the first canvas. Bring it here
    var canvasContext = my.createCanvasContext('canvas')//Unique identification of the second canvas
    canvasContext.scale(0.5, 0.5);//Double the size of the first canvas, because my first one is relatively large and can be adjusted by myself. It is too large and the display is incomplete
    canvasContext.translate(0, 300)//This is the origin offset (the height of the second canvas is 300 and the width is 600)
    canvasContext.rotate(-90 * Math.PI / 180)//Rotate 90 ° counterclockwise to get the horizontal signature
    canvasContext.drawImage(path, 0, 0, 300, 600)//Draw the first one on the second
    canvasContext.draw(true)//Note that it takes time to draw this place. You need to delay obtaining the temporary picture below, otherwise you may get blank because the draw() method is not completed
    setTimeout(() => {
      //Convert the second Canvas content to a temporary picture
      canvasContext.toTempFilePath({
        success(res) {
          console.log("AAAAAFFFFF" + res.filePath)
          //Upload pictures
          dd.uploadFile({
            url: 'https://wms.api.lancjt.com/api/Qiniuupload/upload',
            filePath: res.filePath,
            fileType: 'image',
            fileName: 'file',
            header: {
              'content-type': 'multipart/form-data'
            },
            success: (res) => {
              // res.data is the relevant data returned by your backend
              const data = JSON.parse(res.data)
              var urls =  data.data
              var pages = getCurrentPages(); // Get page stack
              var prevPage = pages[pages.length - 2]; // Previous page
              console.log("AAAAAFFFFF" + urls)
              md5.gone();
              prevPage.setData({
                qian: urls // False data
              })
              my.navigateBack({//return
                delta: 1
              })


            },
            fial: (res) => {
              console.log("AAAAAFFFFF" + res)
            }
          })

        }

      })
    }, 1000)
  },


	/**
	 * Life cycle function -- listening for page loading
	 */
  onLoad: function (options) {

    let canvasName = this.data.canvasName
    let ctx = my.createCanvasContext(canvasName)
    this.setData({
      ctx: ctx
    })
    var query = my.createSelectorQuery();
    query.select('.handCenter').boundingClientRect(rect => {

      this.setData({
        canvasWidth: rect.width,
        canvasHeight: rect.height
      })

      /* Set the canvas background to white background, and do not set the exported canvas background to transparent */
      // console.log(this, 'hahah');
      this.setCanvasBg('#fff');
    }).exec();
  },

####Layout style

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

.wrapper {
  width: 100%;
  height: 95vh;
  margin: 30rpx 0;
  overflow: hidden;
  display: flex;
  align-content: center;
  flex-direction: row;
  justify-content: center;
  font-size: 28rpx;
  margin-left: 20rpx;
}
//First canvas
.handWriting {
  position: relative;
  background: #fff;
  width: 98%;
  z-index: 99;
  height: 83vh;
  border: 4rpx dashed #e9e9e9;
}
.ats{
   position: relative;
}
//Second canvas
.handWriting2{
  position: absolute;
  z-index: 9;
  width: 600rpx;
  height: 300rpx;
  top: 0;
  left: 0;
  /* transform: rotate(90deg); */
  background-color: #FFF;
}

.handRight {
  display: flex;
  height: 100%;
  align-items: center;
  justify-content: center;
  text-align: center;
  width: 100rpx;
}

.handCenter {
  display: flex;
  width: 620rpx;
  flex-direction: column;
  overflow: hidden;
  box-sizing: border-box;
}

.handTitle {
  display: flex;
  color: #000000;
  font-size: 33rpx;
  font-weight: bold;
  transform: rotate(90deg);
  align-items: center;
  justify-content: center;
  text-align: center;
}

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

.handBtn {
  display: inline-flex;
  flex-direction: row;
  justify-content: space-between;
  align-content: space-between;
  text-align: center;
  flex: 1;
}

.delBtn {
  display: flex;
  width: 120rpx;
  height: 75rpx;
  transform: rotate(90deg);
  color: #666;
    align-items: center;
   justify-content: center;
   margin-top: 60rpx;
}

.delBtn image {
  position: absolute;
  top: 13rpx;
  left: 25rpx;
}
.subBtn {
  display: flex;
  width: 120rpx;
  height: 75rpx;
  display: inline-flex;
  transform: rotate(90deg);
  background: linear-gradient(133deg, #5E69FF 0%, #2E37A8 100%);
  box-shadow: 0rpx 6rpx 12rpx rgba(0, 0, 0, 0.16);
  opacity: 1;
  border-radius: 13rpx;
  color: #fff;
  align-items: center;
  justify-content: center;
   margin-top: 60rpx;
}

Keywords: html5 Mini Program wechat

Added by nineseveninteractive on Sat, 18 Dec 2021 05:03:41 +0200