Demo address

Demo address PC side of the project, need to see on the computer oh, and the best use of Chrome open

The introduction

This is a project I did for seniors in March this year, and I spent two months with them in the spring recruitment life. The whole project also learned a lot from it. Now I will share it with you, including some knowledge summaries and pits encountered.

Description of project

The code is based on vuE-CLI code, so we don’t need to talk about routing and VUex, let’s focus on canvas.

Knowledge summary

Drag and drop

Drag here refers to dragging the graphics from the left toolbar to the canvas on the right, complete in three steps:

  1. The dragged element is setdraggable="true";
  2. The dragged element also has three corresponding eventsdragstart drag dragendIf you want to add special effects to these processes, you can try, but it’s mostly used as response data, such as letting the canvas know which element is being dragged in.
  3. Set the placed elementdragover dropTwo events, indicating that the dragged element moves within the scope of the element and the dragged element lands, note heredragoverThe event function needs to be setevent.preventDefault()Prevent a new page from popping upAnd then we can have a good timedropThe event function draws graphics onto the canvas.

HEX => RGBA

Since the colors on the design have no transparency, we need to manually add an alpha of 0.3, otherwise the graphics on the canvas will layer on top of each other, overwriting the lower-level graphics and background images.

functionHex2rgba (hex) {// hex format is as follows#ffffff
      let colorArr = [];
      for(let i = 1; i<7; i += 2){
        colorArr.push(parseInt("0x"+ hex.slice(i,i+2))); // Convert hexadecimal value to base 10}return `rgba(${colorArr.join(",")}`, 0.3); }Copy the code

In addition, if you are interested in learning more about RGBA to RGB, you can check out this blog about RGBA to RGB

Basic Canvas Usage

The following is about canvas. If you are not familiar with its basic usage, you can take a look at JavaScript Canvas canvas

The save and restore

Save can save the current canvas state, including strokeStyle, fillStyle, transformation matrix and clipping area, etc. Restore can restore to the previous state in the canvas state stack, so the change of canvas state made between these two functions is equivalent to being isolated. Does not pollute the external canvas operation.

In this case, it is best to call save before each drawing and restore after each drawing to ensure that each drawing has a pure state.

Here’s a particularly good article that you should read if you don’t like the straight guy. Canvas Learning: Save () and restore()

drawImage

This API can also draw SVG and Canvas to a canvas. Let’s take a look at how to draw SVG.

In fact, the ICONS in the left toolbar of our functional interface are SVG. At first, I wanted to take a screenshot of them and cut them into PNG images with transparent backgrounds, and then draw them on canvas. Later, I found that it would be blurred if I enlarged them.

My own code is hard to post, so let’s look at Dalao’s, drawing a DOM object to canvas, where you stuff the DOM into SVG and draw on canvas. If you just want to draw ready-made SVG, you don’t need to wrap it with foreignObject.

Alternatively, if your SVG has. SVG images, you can call drawImage directly to draw them.

Ellipses and Bezier curves

Canvas already has API for drawing ellipses, but compatibility is not good enough. Bezier curve can be said to be the most elegant one among all other ways of simulating drawing ellipses.

Three dimensional Bezier curves require a start point, two intermediate points, and a stop point. Of course, the start point usually defaults to the current point, so the parameters of bezierCurveTo are the last three points in order. When the four dots form a rectangle, it looks like an ellipse.

 let a = this.width / 2;
 let b = this.height / 2;
 letOx = 0.5 * a, oy = 0.6 * b; this.ctx.beginPath(); // Draw this.ctx.moveto (0, b) counterclockwise from below the vertical axis of the ellipse. // Draw this.ctx.beziercurveto (ox, b, a, oy, a, 0); this.ctx.bezierCurveTo(a, -oy, ox, -b, 0, -b); this.ctx.bezierCurveTo(-ox, -b,-a, -oy, -a, 0);
 this.ctx.bezierCurveTo(-a, oy, -ox, b, 0, b);
 this.ctx.closePath();
 this.ctx.fill();
Copy the code

Here is a fairly complete article on ellipse drawing methods, which can refer to 5 methods of drawing ellipses in HTML5 Canvas

line

Solid line with arrow

Solid lines are easy to draw, but how do I make arrows? Emmm is the calculation of the Angle between a line segment and the X-axis of the canvas, and then draw the corresponding offset triangle at the end of the line segment

DrawArrow (x1, y1, x2, y2) {// (x1, y1) is the starting point of the segment. (x2, y2) is the ending point of the segmentletendRadians = Math.atan((y2 - y1) / (x2 - x1)); EndRadians += ((x2 >= x1)? 90 : -90) * Math.PI / 180; this.ctx.save(); this.ctx.beginPath(); // Start => (x2, y2) this.ctx.translate(x2, y2); this.ctx.rotate(endRadians); this.ctx.moveTo(0, 0); this.ctx.lineTo(5, 15); this.ctx.lineTo(-5, 15); this.ctx.closePath(); this.ctx.fill(); this.ctx.restore(); }Copy the code

Dotted line

  • A more traditional method is to modify the CanvasRenderingContext2D prototype and manually add a dashedLine method. The principle is to draw a solid line from the starting point, then skip a section, moveTo continues to draw a solid line to the next point, and then cycle to the end point to get a dashedLine. See the html5 implementation dotted line
  • In fact, Canvas already supports dotted line drawing, which is used before drawing linessetLineDashYou can specify the style of the dotted line, as detailedCanvas learning: Draw dashed and dotted lines

    But there are some problems with this method. If the Angle is not good or the spacing is too small, the dotted lines will look like solid lines.

Wavy lines

Y = A * sin(ω * x + φ). Specifying A and ω determines the amplitude and frequency of the wavy line (or the height and width of each wave).

letlen = Math.sqrt(width * width + height * height); this.ctx.save(); this.ctx.moveTo(this.start.x,this.start.y); / / starting point for this. The CTX. Translate (this. Start. X, enclosing start. Y); this.ctx.beginPath();let x = 0;
let y = 0;
letamplitude = 5; / / the amplitudeletfrequency = 5; / / frequencywhile (x < len) {
    y = amplitude * Math.sin(x / frequency);
    this.ctx.lineTo(x, y);
    x = x + 1;
}
this.ctx.stroke();
this.ctx.restore();
Copy the code

Draw a Sine Wave in JavaScript

Graphics stack

save

In simple terms, the graphics on our canvas are all instances of a class, stored in an array, cleared with each update and redrawn (optimized later). The attributes that this graph instance needs to save are generally starting and ending coordinates, colors, offset angles, etc., set according to your own needs, and at least one method to dynamically calculate the valid range of the graph so that mouse events can find it.

delete

After a graph instance is selected, it can be deleted from the graph stack array.

rotating

Rotate only needs to rotate an Angle because every time we draw a graph, we move the origin to the center of the graph

Drag and drop to move

Emmm, each graph is not quite the same, if you are interested in the project source bai

Determine if a point is in a quadrilateral

  • The vector method is detailed to determine whether a point is inside a quadrilateral, but this method has some limitations. First, the number of edges of the graph must be determined in advance, and the code will be very long if the number of edges increases. Second, this method only works for convex polygons, so consider the counterexample of concave polygons.
  • Ray method see ray method theory, code implementation is as follows:
function inRange(x, y, points){// points denotes the set of vertices of a polygonlet inside = false;
    for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
        let xi = points[i][0], yi = points[i][1];
        let xj = points[j][0], yj = points[j][1];
        letintersect = ((yi > y) ! == (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);if(intersect) inside = ! inside; }return inside;
}
Copy the code
  • A formula for any point (x,y), counterclockwise rotation of a Angle around a coordinate point (rx0,ry0), the new coordinate is set as (x0, y0), there is the formula: x0= (x-rx0)*cos(a) – (y-ry0)*sin(a) + rx0; y0= (x – rx0)*sin(a) + (y – ry0)*cos(a) + ry0 ; Polar coordinates, if you don’t want to push it, you can just use the formula.

Undo and rollback

I have not done this project, but the idea is not difficult. Using three arrays of past, present and future to save the graph stack, Emm seems to be a bit long to talk about, you can refer to the idea of implementing undo history.

priority

The instances in the graphics stack are drawn one by one, and the graphics drawn later overwrite the previous ones, so there is a priority involved, and the most important ones are drawn later.

Each child element of the Array is an Array. The higher the priority is, the higher the corresponding index value will be. In this way, we can draw all the important graphs in the later part.

The state in VUEX implements bidirectional binding

Normally, the values we use for bidirectional binding are placed in the vue instance’s data because it provides getters and setters by default; However, vuex generally requires computed to read, but computed does not have setter methods by default, so you need to set it manually. The code is as follows:

computed:{
      text : {
        get() {return this.$store.state.text;
        },
        set(value){
          this.$store.commit('setText',value); }}}Copy the code

In the pit of

Html2canvas is a bug

When implementing the function of saving pictures, I want to intercept a piece of DOM content, not just the content of Canvas, so I found the plug-in HTML2Canvas, which can convert DOM to canvas, and then we can convert canvas.todataURL () to an image.

The code to convert and save as a picture download is as follows:

downImg() {
        html2canvas( this.$refs.ground, {
          onrendered: function(canvas) {
            let url = canvas.toDataURL();
            let a = document.createElement('a');
            a.href = url;
            a.download = new Date() + ".png"; document.body.appendChild(a); a.click(); document.body.removeChild(a); }}); }Copy the code

But there is a bug, is downloaded down the picture is not clear, the top left corner of a large blank. So I tried a lot of methods on the Internet, but none of them worked, so I had to start the project from scratch and add things slowly. Finally I found that I changed the prototype of CanvasRenderingContext2D when I drew the dotted line.

Incorrect path when uploading gh-Pages

If you upload to https://XXX.github.io/ (GitHub’s personal blog), the operation is the same as uploading to the server, but if you upload to gh-Pages of a warehouse, then a bunch of problems come up, and the solution steps are as follows:

  1. the.gitignoreIn the file/distDelete, if you ignore it, how can you upload the package file to the master branch?
  2. /config/index.jsIn the build sectionassetsPublicPathThe root directory of gh-pages is not ‘/’, but ‘/ repository name ‘.
  3. If history mode is used, change it to hash mode; otherwise, Github may recognize front-end routes as back-end apis.
  4. There are somestaticIn the picture, using the absolute path, may not be displayed after uploading;
  5. git subtree push --prefix dist origin gh-pagesAfter typing the command, you should see that the upload was successful.

To optimize the

Multilayer canvas

As mentioned above, every time we update the canvas, we always clean it all and then draw it again. Can we optimize the background image and other unchanged content? Emmm, awkward question.

We use multiple canvas layers of the same size to complete, the lower canvas of the lower level is used to draw static graphics such as background images, and the upper canvas of the higher level is used to draw dynamically changing graphics, so that each rendering can be optimized a little bit.

Off-screen rendering

When we drag and drop graphics on the canvas, the general practice is to move the mousemove with the mouse and redraw all the graphics, but actually this process can be divided into two parts, one is the dragged and moved graphics, the other is other graphics; We can dynamically create two canvases and draw the two parts on two off-screen canvases. When mousemove is used, we only need to call drawImage twice (off-screen Canvas)

The code address

Code address although the code quality is poor, I can not bear to look at, but still put it out, in case where do not understand the source can also be turned over