about

Github: github.com/javascript-… Play preview: Canvas-Sign

background

Recently, I received a request temporarily, which requires the client to sign online and save it to the server. The functions roughly include cancellation (that is, every stroke of a stroke needs to support cancellation), change of signature color, and both mobile and PC terminals support. Due to urgent needs, it is of course best to have a ready-made plug-in, after all, learning to be lazy is also a skill (funny). I temporarily found a third-party jSignature plug-in, after a careful study, I found that the plug-in relies on complex, and I also need to introduce jQuery to use this plug-in, because the whole project can not introduce jQuery. The plugin source code is also ES5, or even ES3, at that time I hesitated, can not introduce such a complex plug-in for a signature. Since this requirement ended up with just one image to save in the background, and no digital certificate verification section or anything like that, for my hands-on (and totally forced) self, I could write it myself. Because the demand comes out on Friday and then needs it next Tuesday, it seems that the weekend is another day of quiet overtime.

Analysis of the

Signature is a collection of several operations, starting with the user’s handwritten name, ending with the uploading of the signed image, and also including image processing in the middle, such as reducing aliasing, undo, preview, etc. There is no other choice but canvas.

Hand to tear

In terms of the overall interaction, you need to define touchStart and mousedown to start the drawing. In order to complete the drawing, you also need to handle finger movement or mouse movement, and listen to handle two events: TouchMove and Mousemove.

       const handleMove = (e) = > {
            this.creat({ x: e.clientX - left + 0.5.y: e.clientY - top + 0.5 });
        }
        const handleDown = (e) = > {
            this.creat({ x: e.clientX - left + 0.5.y: e.clientY - top + 0.5 });
        }
        constf fn = {
            mousedown: handleDown,
            mousemove: handleMove,
            / / move
            touchmove: handleMove,
            touchstart:handleDown,
        }

Copy the code

Line drawing

Next, the canvas native has the following API for drawing lines:

  1. Start path (beginPath)
  2. Positioning the starting point (moveTo)
  3. Move the brush (lineTo)
  4. Draw path (Stroke)

Note: Drawing methods in canvas (e.g. stroke,fill) are drawn based on all paths since “last beginPath”. If I comment out the second beginPath in the code below, I’ll notice that the first line segment will be drawn three times after the stroke, because the first stroke and the middle two strokes () are drawn based on all the paths after the first beginPath. In other words, we stroke the first path three times. The first path is red, and the second and third paths are green. Therefore, we also see green overlapped with red. So the last line segment, because you start path again, then you have a new path that doesn’t have anything to do with either of these two.

    var c=document.getElementById("myCanvas");
    var context=c.getContext("2d");
	BeginPath for the first time
    context.beginPath();
    context.moveTo(100.100);
    context.lineTo(200.100);
    context.strokeStyle = "red";
    context.stroke();
    // beginPath the second time
    //context.beginPath();
    context.moveTo(100.130);
    context.lineTo(200.130);
    context.strokeStyle = "green";// The first line segment includes red, green, and white
    context.stroke();
    context.stroke();
	// The third beginPath
    context.beginPath();
    context.moveTo(100.160);
    context.lineTo(200.160);
    context.strokeStyle = "black";
    context.stroke();// Stroke several times so that the color of the line between the last beginPath and the next beginPath becomes darker
    context.stroke();
    context.stroke();
    context.stroke();
Copy the code

The effect is as follows:

  1. No matter where you move your brush with moveTo, as long as you don’t beginPath, you are always drawing a path.
  2. Functions such as fillRect and strokeRect, which draw independent regions directly, also do not interrupt the current path.

For details, please refer to canvas MDN. If the image you draw is different from what you expected, please check whether there is a reasonable beginPath. Speaking of beginPath, by the way, closePath has nothing to do with beginPath! ClosePath does not mean to end a path, but to close it. It tries to close the entire path by connecting a path from the end of the current path to the beginning. As follows:

    const c=document.getElementById("myCanvas");
    const context=c.getContext("2d");
    context.beginPath();
    context.moveTo(100.100);// Make it seven o 'clock
    context.lineTo(200.100);// Move the brush
    context.lineTo(230.120);// Move the brush
    context.strokeStyle = "red";
Copy the code

Effect:

  //context.closePath();
    context.stroke();
Copy the code

After the comment is removed, the effect is to create a closed path from the start point to the end point:

    context.closePath();
    context.stroke();
Copy the code



BeginPath and closePath are not related to each other.

Unconsciously said more, then came to the point:

With the start and Move events, the idea of drawing lines is much clearer. When pressing the button, we need to reset the beginPath and move the pen moveTo, because pressing the button is a complete drawing process even if it does not move:

    const handleDown = (e) = > {
        // Reset the brush when the key is pressed
        this.ctx.beginPath();
        //isMouseDown determines if the key is pressed and ready to draw
        this.isMouseDown = true;
        const position = { x: e.clientX - left + 0.5.y: e.clientY - top + 0.5 };
        this.ctx.moveTo(position.x,position.y)
        this.creat({ x: position.x, y: position.y });
    }
Copy the code

Mobile processing:

  const handleMove = (e) = > {
        // Check whether the left mouse button is pressed down on the PC
        if ((!this.isMouseDown || e.which ! =1) && !mobile) return;
        e = mobile ? e.touches[0] : e;
        this.creat({ x: e.clientX - left + 0.5.y: e.clientY - top + 0.5 });
    }
   const creat = (position = { x, y }) = > {
         ctx.lineTo(position.x, position.y);
         ctx.stroke();
    }
Copy the code

Left and top in the above code are not built-in variables; they represent the pixel distance from the canvas to the left and top of the screen, respectively, and are mainly used to convert screen coordinate points to canvas coordinate points. Here’s one way to get it:

const { left, top } = this.canvas.getBoundingClientRect();
Copy the code

Set up the

The next step is to change the brush Settings to support the native Canvas brush property:

    // Provide a condition to save the current style to avoid conflicts with existing CTX styles when importing JSON
    setLineStyle(style = {},isSaveLineStyle = true) {
        const ctx = this.ctx;
        constlineStyle = isObject(style)? {... this.lineStyle, ... style } :this.lineStyle;
        if(isSaveLineStyle){
            this.lineStyle = lineStyle;
        }
        Object.keys(lineStyle).forEach(key= > {
            ctx[key] = lineStyle[key];
        });
        ctx.beginPath();
        return this;
    }
Copy the code

Export json

Drawing lines requires the configuration of path and brush, which is a set of coordinates, and brush configuration, which is the individual properties passed in when setLineStyle is used. This results in a JSON data structure:

    const json = [{
        lineStyle: {},position:[]
    }]

Copy the code

There are three ways to handle JSON: start drawing, middle drawing, and end drawing. Start drawing: when the mouse is down, that is, a point, the current value of pSUh into the JSON array. Drawing: Adds the move point information to position in the last data in the array. End drawing: Instead of adding data to the array, save the current lineStyle information.

    setJson(value, type) {                                           
        const dataJson = this.dataJson;              
        const jsonLength = dataJson.length - 1;    
        switch (type) {                            
            case 'moving':                             
                dataJson[jsonLength].position.push(value); 
                break;                                     
            case 'end':                                
                dataJson[jsonLength].lineStyle = value;    
                break;                                    
            case "start":                              
                dataJson.push(value);                      
                break; }}Copy the code

To optimize the

To make the move more fluid, consider the requestAnimationFrame optimization:

   const raf = window.requestAnimationFrame;
   const move = raf?(e) = >{
        raf(() = > {
            handleEvent.handleMove(e);
          });
    }:handleEvent.handleMove;
Copy the code

Wrote last

This article introduces the main process of online signing implementation. The core code can be found here: Canvas-sign. If you like it, please click star, thank you. So much for this question. If you have any questions or good ideas, please leave a comment below.