JS case: support Canvas electronic signature function on PC and Mobile

preface:
During this period of time, a new requirement was encountered during the project iteration. A Pc version electronic signature function was implemented based on react, and pictures were generated and uploaded. So I thought signature_pad And used this plug-in in the project
I have to say that the wheels made by others are really fragrant. Out of curiosity, I want to use native to realize the function of electronic signature

The following is the implementation process

HTML and css can be referenced Source code , there is no more introduction here

First introduce eventBus To facilitate code decoupling
Then implement the Base class to store public methods and attributes. What common attributes or methods can be added later

//Base classes: public methods and properties
import event from './eventBus.js'
export default class Base {
    constructor(canvasEle, dom = document) {
        this.event = event //Register publish subscription
        this.canvasEle = canvasEle //Canvas label to be operated
        this.dom = dom //dom
        return this;
    }
}

After completion, we first implement the electronic signature function of Pc version, and create a new PcPrint inherited from Base, referring to the previously written Mouse drag case , realize the drag and drop function on canvas and publish the coordinates of event results.
clearDefaultEvent function and getClient function are implemented in Base class

// PC side, mouse event
import Base from './base.js'
let that = null
export default class PcPrint extends Base {
    constructor(ele, dom) {
        super(ele, dom)
        that = this //Register global this
        this.init()
        return this;
    }
    init() {
        that.canvasEle.addEventListener('mousedown', that.onMouseDown)
    }
    onMouseDown(e = event) {
        that.clearDefaultEvent(e)
        that.dom.addEventListener('mouseup', that.onMouseUp) //Add mouseup to dom to avoid other problems caused by moving out of the canvas when the mouse clicks
        that.canvasEle.addEventListener('mousemove', that.onMouseMove)
        that.event.emitEvent('pointStart', that.getClient(e)) //Trigger signing start event
    }
    onMouseUp(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointEnd') //Trigger end signing event
        that.canvasEle.removeEventListener('mousemove', that.onMouseMove) //Remove move event
    }
    onMouseMove(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointMove', that.getClient(e)) //Trigger signing event
    }

}

Add the following code to the Base class:

    /**
     * Default and cancel bubble events
     * @param e Event object
     */
    clearDefaultEvent(e) {
        e.preventDefault()
        e.stopPropagation()
    }
    /**
     * Get the coordinates of the event element from the visible area of the body
     * @param target Event target
     */
    getClient(target) {
        return {
            x: target.clientX,
            y: target.clientY
        }
    }

Then, we subscribe to the three publications thrown by the event, create a new Print class, and draw the obtained coordinates through canvas

import Base from "./Base.js"
import PcPrint from './pc.js';
// import MobilePrint from './mobile.js';
let that = null
export default class Print extends Base {
    constructor(canvasEle, options, dom) {
        super(canvasEle, dom)
        that = this
        this.options = options //Configure the brush color, thickness and whether to turn on the mobile terminal or PC terminal,
        this.init() //Initialize properties, configure, register, publish and subscribe, etc
        this.initCanvas() //Initialize canvas
        return this
    }
    init() {
        //Pc and Mobile enable switches
        this.Pc = this.options.Pc ? (new PcPrint(this.canvasEle)) : null
        // this.Mobile = this.options.Mobile ? (new MobilePrint(this.canvasEle)) : null
        this.point = null //Store last coordinates
        this.event.onEvent('pointMove', that.pointMove) //Subscription signing event
        this.event.onEvent('pointStart', that.pointStart) //Subscription signing start event
        this.event.onEvent('pointEnd', that.pointEnd) //Subscription signing end event
    }
    initCanvas() {
        this.clientRect = this.canvasEle.getBoundingClientRect() // Gets the offset of the label from the visible area
        this.canvasEle.width = this.canvasEle.parentNode.offsetWidth //Set to the width of the parent element
        this.canvasEle.height = this.canvasEle.parentNode.offsetHeight //Set to the height of the parent element
        this.context = this.canvasEle.getContext('2d')
        this.context.strokeStyle = this.options.color; // line color
        this.context.lineWidth = this.options.weight; // Line width
    }
    pointStart(point) {
        that.point = that.shiftingPosition(point, that.clientRect) //Initialization start position
    }
    pointEnd() {
        that.point = null //Clear start position
    }
    pointMove(point) {
        that.canvasDraw(that.shiftingPosition(point, that.clientRect)) //Signature effect
    }
    canvasDraw(point) { //Canvas operation
        this.context.beginPath() //New (reset) path
        this.context.moveTo(this.point.x, this.point.y) //The starting point of canvas painting moves to the previous coordinate
        this.context.lineTo(point.x, point.y) //Canvas from previous coordinate to current coordinate
        this.context.stroke() //Draw from moveTo to lineTo
        this.context.closePath() //Creates a path from the current coordinate back to the previous coordinate
        that.point = point //Assign this coordinate to the previous one in the next move
    }
}

Considering the offset problem of canvas, the shiftingPosition function is added to Base to solve the coordinate offset problem during canvas drawing

   /**
     * Offset canvas offset
     * @param point Current coordinates
     * @param shift Offset
     */
    shiftingPosition(point, shift) {
        return {
            x: point.x - shift.left,
            y: point.y - shift.top
        }
    }

Finally, instantiate the electronic signature in index

<script type="module">
    import Print from "./js/print.js"
    new Print(printBox,{
        Pc:true,
        Mobile:true,
        color:'lightcoral',
        weight:5
    })
</script>

The effect is as follows:

After the Pc end is implemented, the Mobile end has the same code. In addition to not using the event type, there is also the multi finger touch support of the Mobile end, and touchevent supports double finger events. At this time, we need to judge whether to single finger input

// Mobile terminal, touch event
import Base from './base.js'
let that = null
export default class MobilePrint extends Base {
    constructor(ele, dom) {
        super(ele, dom)
        that = this //Register global this
        this.init()
        return this;
    }
    init() {
        that.canvasEle.addEventListener('touchstart', that.onTouchStart)
    }
    onTouchStart(e = event) {
        that.clearDefaultEvent(e)
        that.canvasEle.addEventListener('touchend', that.onTouchEnd) //Touchend is not added to dom like pc, because touchmove is triggered between touchstart and touchend. As long as touchend is triggered, touchmove will fail
        that.canvasEle.addEventListener('touchmove', that.onTouchMove)
        that.event.emitEvent('pointStart', that.getClient(e.touches[0])) //Here, we can judge whether there is only one e.touches (e.touches means several fingers touch)
    }
    onTouchEnd(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointEnd')
        that.canvasEle.removeEventListener('touchmove', that.onTouchMove)
    }
    onTouchMove(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointMove', that.getClient(e.touches[0]))
    }
}

Effects achieved at the mobile end:

last:
Attach the source code address: Gitee

Thank you for reading. If this article is helpful to you, I hope it can be supported by the third company. Your support is the driving force of my creation. At the same time, you are welcome to make suggestions and corrections

Keywords: Javascript Front-end html

Added by bubatalazi on Mon, 28 Feb 2022 14:24:18 +0200