Mini PS small program – integrated open poster, ink electronic signature, picture drag and drop can be eaten separately

Time:2020-9-12

Minasan, oh, ha~
Personal production, this article mainly explains the production ideas and implementation code of the recently compiled small program based on uni app framework, which integrates multi-directional editing, ink electronic signature and open poster.


catalog

1. Full source link
2. Realization ideas
3. Core code
3-1. Multi directional editing of pictures and texts
3-2. Ink electronic signature
3-3. Open Poster
Summary
4. Effect display and experience


1. Full source link:

Complete code: https://github.com/TensionMax/mini-ps
Among them, text editing, picture editing, ink electronic signature and open poster can be eaten separately, including document description.


2. Realization ideas

image

The tool consists of five modules: text editing, picture editing, ink electronic signature, control and open poster
1. The text parameter objects set by the text editing module are inserted into the text queue.
2. The image parameter object set by the image editing module is inserted into the picture queue.
3. After the ink electronic signature module completes the drawing, it turns to use the canvas to tempfilepath to convert into a temporary picture. After obtaining the parameters, it is inserted into the image queue, and can also be directly exported.
4. Use the control module to adjust the parameters of / text queue and picture queue.
5. Open poster module, use the parameters of the console to draw the effect on the PS Sketchpad to canvas, and then use it againcanvasToTempFilePathConvert to image export.


3. Core code

3-1. Text / picture editing module

The text / picture editing module is mainly used to realize the function of moving / zooming, and the others are desserts,
Because the two modules have similar functions, this article only explains the picture editing module.

HTML

stayimageListFor each bound event, use the$eventTo call the parameters of the event itself, where$eventOftouchesorchangedTouchesInclude the location parameters we need. Examples are as follows:

touches:[{
        Clientx: 14 // horizontal distance from the top left corner of the display area (excluding the top bar)
        Clienty: 16 // vertical distance from the top left corner of the display area (excluding the top bar)
        Pagex: 14 // horizontal distance from the top left corner of the entire page (excluding the top bar)
        Pagey: 16 // vertical distance from the top left corner of the entire page (excluding the top bar)
        },
        {
        clientX: 14
        clientY: 16
        pageX: 14
        pageY: 16
        }]

touchesThe length of 2 represents the double finger touch, and the double finger zoom effect can be achieved by determining the change direction of the double finger touch point. Because each label is set tostyle="position: absolute"So you only need to update according to the location parametersx、y、w、hthat will do

Digression – performance issues

Moving DOM multiple times at a time affects performance
——Virtual DOM
Why not delegate events
——No need, Vue has already optimized it for us, and we will consider it when the performance is greatly affected

Picture editing demo

3-2. Ink electronic signature board

becausetouchmoveThe trigger frequency and accuracy of the event in the real machine of the small program is very confusing. It is not very good to determine the line width according to the speed. I have to use other ways to achieve it, although the effect is not perfect.

image

In fact, the idea is to achieve ink effect through multiple cycles, and the length and width of each cycle are different.

HTML

JAVASCRIPT

export default {
data() {
    return {
        Linewidth0: 5, // the recommended initial line width is 1 ~ 5
        ctx: null,
        X0: 0, // initial abscissa or the abscissa of the touch point in the previous touchmove event
        Y0: 0, // the initial ordinate or the ordinate of the touch point in the previous touchmove event
        T0: 0, // initial time or the time of the last touchmove event
        V0: 0, // initial rate or occurrence rate between touchmove events
        Linewidth: 0, // dynamic line width
        Keenness: 5, // the recommended ink level is 0 ~ 5
        k: 0.3, // ink factor, that is, the change of line width each time a line is drawn
    }
},
onReady() {
    this.ctx = uni.createCanvasContext('canvas', this);
    this.ctx.setLineCap('round')
},
methods: {
    //Set initial value
    touchStart(e) {
        this.lineWidth = this.lineWidth0
        this.t0 = new Date().getTime()
        this.v0 = 0
        this.x0 = e.touches[0].clientX
        this.y0 = e.touches[0].clientY
    },

    touchMove(e) {
        let dx = e.touches[0].clientX - this.x0,
            dy = e.touches[0].clientY - this.y0,
            ds = Math.pow(dx * dx + dy * dy, 0.5),
            dt = (new Date().getTime()) - this.t0,
            V1 = DS / dt; // rate of occurrence between this.v0 initial rate or touchmove event
        if ( this.keenness  ===0) {// when the ink is 0
            this.ctx.moveTo(this.x0, this.y0)
            this.ctx.lineTo(this.x0 + dx, this.y0 + dy)
            this.ctx.setLineWidth(this.lineWidth)
            this.ctx.stroke()
            this.ctx.draw(true)
        } else {
            //Because of the trigger frequency of touchmove, the for loop is used here, and the principle is shown in the figure
            //The K here is because
            let a = this.keenness
            if (this.keenness > 5) {
                a = 5
            }
            for (let i = 0; i < a; i++) {
                this.ctx.moveTo(this.x0 + dx * i / a, this.y0 + dy * i / a)
                this.ctx.lineTo(this.x0 + dx * (i + 1) / a, this.y0 + dy * (i + 1) / a)
                //At this time, the rate of occurrence between touchmove events and that of the previous event is compared
                if (v1 < this.v0) {
                    this.lineWidth -= this.k
                    if (this.lineWidth < this.lineWidth * 0.25) this.lineWidth = this.lineWidth * 0.25
                } else {
                    this.lineWidth += this.k
                    if (this.lineWidth > this.lineWidth * 1.5) this.lineWidth = this.lineWidth * 1.5
                }
                this.ctx.setLineWidth(this.lineWidth)
                this.ctx.stroke()
                this.ctx.draw(true)
            }
        }
        this.x0 = e.touches[0].clientX
        this.y0 = e.touches[0].clientY
        this.t0 = new Date().getTime()
        this.v0 = v1
    },
    touchEnd(e) {
        this.x0 = 0
        this.y0 = 0
        this.t0 = 0
        this.v0 = 0
    }
}
}

Most of you are using the basic canvas API. Note that the drawing unit is px.

Ink electronic signature demo

3-3. Open poster module

If the wechat applet is a silver golden beach, then as of January 6, 2020 or in the future, the canvas of the small program will be the piece of glass on the golden beach that is full of unknowns——Lu Xun

Speaking of the small program canvas, the bugs are not common, some of them are not common. I will explain them in the code comments.

HTML

Related introduction

Spread syntax
Async function
If the picture is a network path, remember to get a temporary path.

//Don't forget to add async before the function
let src = 'https://s2.ax1x.com/2020/01/05/lrCDx0.jpg'
src = (await uni.getImageInfo({src}))[1].path;

JAVASCRIPTOutput field section

//For the convenience of setting, the following units are mainly rpx except angle
data() {
    return {
        canvasW:720,
        canvasH:1000,
        img:[{
            src: 'https://s2.ax1x.com/2020/01/05/lrCDx0.jpg',
            x: 0,
            y: 0,
            w: 100,
            h: 100,
            r: 50, // circle angle
            Degrees: 30, // degree of rotation
            Mirror: true // mirror
            }],
        text:[{
                content: 'TensionMax',
                x: 50,
                y: 50,
                w: 100,
                Lineheight: 35, // line spacing
                color: '#000000',
                size: 28,
                Weight: 'normal' // font thickness
                Linethrough: true, // whether to run through
            }],
        ctx: null,
        k: Null // unit conversion factor
    };
}

JAVASCRIPTUnified unit conversion method of rpx or upx and PX

px2rpx() {
    //When there is only one parameter in the conversion, the value is returned directly, such as
    //Returns an array when it is not one, and then expands it into several parameters using spread syntax
    // Math.floor () is to prevent data disorder on Android. There is no such bug in developer tools
    if (arguments.length === 1) return Math.floor(arguments[0] / this.k)
    let params = []
    for (let i of arguments) {
        params.push(Math.floor(i / this.k))
    }
    return params
},
rpx2px() {
    if (arguments.length === 1) return Math.floor(arguments[0] * this.k)
    let params = []
    for (let i of arguments) {
        params.push(Math.floor(i * this.k))
    }
    return params
},

JAVASCRIPTFunctions for drawing pictures

async drawImg() {
this.ctx.setFillStyle('#FFFFFF')
this.ctx.fillRect (0, 0, ... this.rpx2px ( this.canvasW , this.canvasH ))// draw background
for (let i of  this.img ){// for loops to draw pictures
    i.src = (await  uni.getImageInfo ({SRC: i.src})) [1]. Path; // get the temporary path of the image
    this.ctx.save () // save the current drawing content
    If (i.mirror) {// if mirror is set
        //Because the translate attribute of canvas is based on the origin (the initial origin is the upper right corner)
        //So you need to move the origin to the center of the picture first, and then restore it after changing
        //The same is true for rotation
        this.ctx.translate(...this.rpx2px(i.x + i.w / 2, i.y + i.h / 2))
        this.ctx.scale(-1, 1)
        this.ctx.translate(...this.rpx2px(-i.x - i.w / 2, -i.y - i.h / 2))
    }
    If (i.degrees) {// if rotation is set
        this.ctx.translate(...this.rpx2px(i.x + i.w / 2, i.y + i.h / 2))
        this.ctx.rotate(i.degrees * Math.PI / 180)
        this.ctx.translate(...this.rpx2px(-i.x - i.w / 2, -i.y - i.h / 2))
    }
    i.r = i.r ? i.r : 0
    this.radiusRect (... this.rpx2px (I, I.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, R.I, I, R.I, I
    this.ctx.clip () // crop
    this.ctx.drawImage(i.src, ...this.rpx2px(i.x, i.y, i.w, i.h))
    this.ctx.restore () // restore non Crop Region
}
this.ctx.draw(true) 
}

radiusRect(x, y, w, h, r) {
    if (r > w / 2 || r > h / 2) {
        r = Math.min(w, h) / 2
    }
    this.ctx.beginPath();
    this.ctx.moveTo (x, y); // move the operation point to the upper left corner
    this.ctx.arcTo (x + W, y, x + W, y + R, R); // draw the arc in the upper right corner
    this.ctx.lineTo (x + W, y) // can be omitted, but due to the small program bug of Android, it is left, the same below.
    this.ctx.arcTo (x + W, y + H, x + W - R, y + H, R); // draw the arc in the lower right corner
    this.ctx.lineTo (x + W, y + H) // can be omitted
    this.ctx.arcTo (x, y + H, x, y + H - R, R); // draw the arc in the lower left corner
    this.ctx.lineTo (x, y + H) // can be omitted
    this.ctx.arcTo (x, y, x + R, y, R); // draw the arc in the upper left corner
    this.ctx.lineTo (x, y) // can be omitted
},

Draw custom text

It’s a little troublesome to draw text. The main reason is that canvas doesn’t automatically help us to wrap and typeset. There are too many similar implementation methods on the Internet, so I won’t talk about it and put it in demo directly.

Open poster demo

Summary

Now that we know how to customize the parameters of these components, we only need a parent component as the console to adjust their parameters. We can use props and sync modifiers to achieve parent-child communication. Of course, if you want to do more complicated work, you can consider using vuex to transfer parameters. Next, we can implement the tedious business logic according to this idea.


4. Effect display and experience

The effect picture is as follows. If you have any questions, please come to the comments section below for discussion.
image

image

image