uni-app electronic signature function

Foreword

With the development of the times, the previous handwritten signatures have gradually derived electronic signatures, such as electronic signatures in systems such as DingTalk, and electronic signatures have the same legal effect as paper handwritten signatures.

How do we implement electronic signatures as a front end? In fact, an important auxiliary tag has appeared in html5 – canvas

Talk about canvas

canvas is a label that can be drawn on it through JavaScript, by providing context (context) and API Drawing, in this process canvas acts as a canvas and other roles

Preparation

First of all, when we see this requirement, what we think of is when the mouse is pressed, start to draw the line, and continue to draw this line during the movement, so the first thing we think of is the mouse Press the mobile event, so we first use the mouse event to achieve

The first step, of course, is to create the canvas element in the body.

<canvas id="cvs"></canvas>

Then get this element and mount it on the event of pressing, moving and lifting

const cvs = document. getElementById('cvs')
cvs. addEventListener('mousedown', (e) => {<!-- -->})
cvs.addEventListener('mousemove', (e) => {<!-- -->})
cvs.addEventListener('mouseup', (e) => {<!-- -->})

We need to start executing after the mouse is pressed, so we define a variable for recording whether the mouse is pressed, open when it is down, and close when the mouse is up, if If it is not pressed, then we do nothing while moving

const cvs = document. getElementById('cvs')
let isDownin = false
cvs.addEventListener('mousedown', (e) => {<!-- -->
    isDownin = true
})
cvs.addEventListener('mousemove', (e) => {<!-- -->
    if(!isDownin) return
})
cvs.addEventListener('mouseup', (e) => {<!-- -->
    isDownin = false
})

Start drawing

The idea to draw is very simple. When we press the mouse, we start to draw a line and move the coordinates to the current click point. During the movement, a lot of points will be generated. Connect these points Just make a line, we first need to use moveTo to move the coordinates to the point under our mouse, and then use lineTo to connect these points into a line during move , and finally use stroke to draw it

const cvs = document. getElementById('cvs')
const ctx = cvs.getContext('2d')
let isDownin = false

cvs.addEventListener('mousedown', (e) => {<!-- -->
    isDownin = true
    ctx.moveTo(e.pageX, e.pageY)
})
cvs.addEventListener('mousemove', (e) => {<!-- -->
    if(!isDownin) return
    ctx.lineTo(e.pageX, e.pageY)
    ctx.stroke()
})
cvs.addEventListener('mouseup', (e) => {<!-- -->
    isDownin = false
})

The above few lines of code complete a basic signature version, and the idea is very clear

Custom style

We want to define the color and width of the pen. How can we do it? We can extract it into a method, and we can directly change the color and width we need in the future.

function drawLine(x,y){<!-- -->
    ctx.beginPath();
    ctx.lineWidth = 5;
    ctx.strokeStyle = '#fff';
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(x, y);
    ctx.stroke();
    lastX = x;
    lastY = y;
}

lineWidth sets the pen width
strokeStyle sets the color of drawing line

But we have seen the addition of lastX and lastY, this is because we have extracted a separate method, and we need to moveTo move each time we draw Go to a new point to continue drawing, which is a little different, and then we can pass the coordinates in the process of mousemove, of course, if you want to do other beautification, such as lineJoin, lineCap and other attributes, set whether there is a rounded edge at the intersection of the line, etc. You can try to choose these operations yourself.

Realize writing undo, clear function

Let’s first look at the relatively simple emptying function
Clearing the canvas is relatively simple. From the upper left corner of (0,0) to the far right, perform clearRect to achieve

ctx. clearRect(0, 0, width, height)

So how to implement the rollback? If we want to implement the rollback, we have to keep the track of the user every time he draws a stroke. If we cancel next time, we need to put the data back. How to achieve it?

Copy the pixel data of the specified rectangle on the canvas by getImageData(), and then put the image data back on the canvas by putImageData()

We create a cacheData user record. Whenever we press the mouse, it means that the last transaction has ended. We get all the previous data through getImageData and push to cacheData, then when the user clicks back, we only need to remove the last item, and then put it back through putImageData

ctx.getImageData(0, 0, width, height);
ctx.putImageData(cacheData.pop(), 0, 0);

Save signature as image

As canvas actually provides two methods to export images

  1. toDataURL, this method is synchronous, converted to base64, and then we can export
  2. toBlob can convert it into a blob flow file, this method is asynchronous

In daily life, we recommend toBlob. I have seen others say that if toBlob can appear earlier, there is no need for the toDataURL method. On the one hand, the first api is easy to misspell. On the other hand, this is a synchronous method. If it is too large, it will be blocked, so we will give priority to toBlob in the future. It is very simple to use , convert it to a blob stream file, get it in the callback function, then we create an a tag, and then get a memory URL of the current file through URL.createObjectURL, and then you can download it.

cvs.toBlob((blob) => {<!-- -->
    const a = document. createElement('a');
    document.body.append(a);
    a.download = `Signature.png`;
    a.href = URL.createObjectURL(blob);
    a. click();
    a. remove();
  });

We can let users download it by themselves, or upload it to the server for storage, then our function of generating pictures is completed.

It seems that Dongfeng is ready before everything is ready. According to the past practice, we can go online and go online, but I believe that the product will find you again soon after it goes online and ask why it can’t be used on the phone. At this time, you should suddenly realize that we The mouse event only supports pc, so we should be compatible with mobile phones at this time.

Compatible with mobile phones

We know that the mobile terminal corresponding to the mouse event is a touch event, so before using it, we should first judge the environment. When the environment is judged to be the mobile terminal, we use mouse corresponds to touchstart/touchmove/touchend/touchcancel and other events. At the same time, the position of X and Y coordinates is changed relative to each other. For example, the X coordinate of the mobile terminal is e.changedTouches[0].clientX, at this time we also support the mobile terminal, and a relatively complete electronic signature function has been realized

Complete code

Environment uni-app

Create a canvas node

<canvas class="form-content__canvas" canvas-id="canvas_sign" @touchstart="touchstart"
@touchmove="touchmove" @touchend="touchend" disable-scroll="true"></canvas>

Touch start

touchstart(e) {<!-- -->
if (!this.isInit) {<!-- -->
this.isInit = true
this. autographClick(1);
}
let startX = e.changedTouches[0].x
let startY = e.changedTouches[0].y
let startPoint = {<!-- -->
X: startX,
Y: startY
}
this.points.push(startPoint)

//Every touch starts, open a new path
this. canvasCtx. beginPath()
}

Get the initial coordinates, x and y, and save them. Note that beginPath() should be called after each touch.
Touch move (touchmove)

touchmove(e) {<!-- -->
let moveX = e.changedTouches[0].x
let moveY = e.changedTouches[0].y
\t\t\t\t
let movePoint = {<!-- -->
X: moveX,
Y: moveY,
T: new Date(). getTime(),
W: (MAX_LINE_WIDTH + MIN_LINE_WIDTH) / 2
}

this.points.push(movePoint) //save point

if (lastPoint) {<!-- -->
// console.log(lastPoint.T, movePoint.T)
movePoint.W = this.calcLineWidth(movePoint); // Reassign the width, override the default value
this.canvasCtx.beginPath();
this.canvasCtx.strokeStyle = '#000';
this.canvasCtx.lineCap = 'round';
this.canvasCtx.lineJoin = 'round';
this.canvasCtx.lineWidth = movePoint.W;
this.canvasCtx.moveTo(lastPoint.X, lastPoint.Y);
this.canvasCtx.lineTo(movePoint.X, movePoint.Y);
this. canvasCtx. stroke();
}
lastPoint = movePoint; // Save the current point as the previous point before the end
\t
let len = this.points.length
if (len >= 2) {<!-- -->
this.draw() //draw path
}

}
\t\t\t\t

Get the coordinates of the movement, and save the coordinates, time and brush width.

Stroke effect (calcLineWidth)

const MAX_V = 1; // maximum writing speed
const MIN_V = 0; // minimum writing speed
const MAX_LINE_WIDTH = 16; // maximum stroke width
const MIN_LINE_WIDTH = 4; // minimum stroke width
const MAX_LINE_DIFF = .03; // maximum difference in stroke width between two points
let context = null; // canvas context
let lastPoint = null; // object containing stroke information of last point


calcLineWidth(movePoint) {<!-- -->
let consuming = movePoint.T - lastPoint.T; // time spent between two points
if (!consuming) return lastPoint.W; // If the consumption time of the current point is 0, return the width of the previous point.
\t
// the maximum width of the current point
let maxWidth = Math.min(MAX_LINE_WIDTH, lastPoint.W * (1 + MAX_LINE_DIFF));
// The minimum width of the current point, the speed is fast when it becomes thinner, so the width changes slightly faster
let minWidth = Math.max(MIN_LINE_WIDTH, lastPoint.W * (1 - MAX_LINE_DIFF * 3));
// distance between two points
let distance = Math.sqrt(Math.pow(movePoint.X - lastPoint.X, 2) + Math.pow(movePoint.Y - lastPoint.Y, 2));
/*current point speed*/
let speed = Math.max(Math.min(distance / consuming, MAX_V), MIN_V);
/* current point width */
let lineWidth = Math.max(Math.min(MAX_LINE_WIDTH * (1 - speed / MAX_V), maxWidth), minWidth);

return lineWidth;
}

In the process of drawing, the width is calculated by the distance and speed between two points, and then the drawing is performed, which is the effect of the stroke. You can adjust the initial value by yourself and set it to the effect you want most.

Draw handwriting (draw)

draw() {<!-- -->
let point1 = this. points[0]
let point2 = this. points[1]
this. points. shift()
this.canvasCtx.moveTo(point1.X, point1.Y)
this.canvasCtx.lineTo(point2.X, point2.Y)
this. canvasCtx. stroke()
this. canvasCtx. draw(true)
this. hasSign = true
}

  1. In order to ensure real-time display of handwriting, handwriting must be drawn while moving;
  2. In order to ensure the continuity of handwriting, two points are selected from the path set each time as the start point (moveTo) and end point (lineTo);
  3. Use the last point as the starting point for the next drawing (that is, clear the first point).

Touch end

touchend() {<!-- -->
this. points = []
this. canvasCtx. draw(true)
lastPoint = null;
}

Finally, don’t forget to empty it every time you end it.

article reference
https://blog.csdn.net/Robergean/article/details/128863208
https://juejin.cn/post/7146598432285655054