An eight thousand word super detailed Canvas from zero to one starter guide

Introduction of Canvas

Provides a way to draw graphics using JavaScript and HTML

elements. It can be used for animation, game graphics, data visualization, image editing, and real-time video processing. In practical applications, we seldom have the requirement to operate canvas directly. In most cases, we operate canvas libraries directly, such as Echarts and some picture clipping plug-ins. Recently, the subject is reading books about canvas and preparing to learn about canvas from the beginning. This article mainly summarizes some canvas and records some common API methods.

Note: all of my code examples are based on TS + Webpack environment, there are friends who do not understand can gotsAnd [webpack 】 (Webpack.docschina.org/)

  • Compatibility: IE9+, can be compatible with IE7, IE8 through this plug-in

1. Canvas draws geometric graphics

1.1 Steps to create a Canvas element

  1. Create canvas elements with a default width of 300 and height of 150
  2. Gets the context object context
  3. I’m going to draw my axes with the origin in the upper left corner of the screen
const canvas = document.querySelector<HTMLCanvasElement>("#canvas") | |new HTMLCanvasElement();
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
Copy the code

Note that when setting the canvas width and height here, do not use style to set it, because after setting this, we will get the default canvas width and height, so we need to set the width and height directly on the canvas

<canvas id="canvas" width="500" height="300" />
Copy the code

1.2 Drawing straight lines

1.2.1 a straight line

  • Ctx.moveto (x, y): Moves the brush to the (x, y) position
  • Ctx. lineTo(x1, y1): Let the brush draw a line from (x, y) to (x1, y1)
  • Ctx.stroke () : Start drawing
const canvas = document.querySelector<HTMLCanvasElement>("#canvas") | |new HTMLCanvasElement();
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
ctx.moveTo(0.0)
ctx.lineTo(300.300)
ctx.stroke()
Copy the code

Draw straight lines

ctx.moveTo(200.100)
ctx.lineTo(400.100)
ctx.lineTo(400.50)
ctx.moveTo(200.300)
ctx.lineTo(400.300)
ctx.stroke()
Copy the code

If there is no moveTo, the drawing will continue from the previous lineTo position. If there is, the brush will moveTo the moveTo position to draw the lineTo

1.2.2 rectangle

  • Ctx.strokerect (x, y, width, height) : Draw a hollow rectangle with width length and height starting from the position (x, y)
  • Ctx. strokeStyle = “”; Modify the color of the edge
ctx.strokeStyle = "red";
ctx.strokeRect(100.100.200.100);
Copy the code

The order of strokeStyle and strokeRect cannot be reversed

  • Ctx.fillrect (x, y, width, height) : Draw a solid rectangle with width length and height starting at (x, y) position
  • FillStyle = “”; Modify the color of the fill
ctx.fillStyle = "purple"
ctx.fillRect(100.100.200.100);
Copy the code

The order of fillStyle and fillRect cannot be reversed

  • Rect (x, y, width, height) : Start at (x, y) and draw a rectangle with width and height. Unlike the above two rectangles, the rectangle drawn by this method is not immediately displayed. You need to call either fill() or stroke() to display the stroke and fill colors
ctx.strokeStyle = "blue";
ctx.fillStyle = "pink"
ctx.rect(100.100.200.100)
ctx.fill();
ctx.stroke();
Copy the code

  • Ctx.clearrect (x, y, width, height) : Clears rectangles from position (x, y) width length, height height
  • This clears only the fill color that is cleared, not the stroke color
ctx.strokeStyle = "blue";
ctx.fillStyle = "pink"
ctx.rect(100.100.200.100)
ctx.fill();
ctx.stroke();
ctx.clearRect(120.120.160.60)
Copy the code

1.2.3 polygon

  • Polygons are implemented via lineTo and moveTo

  • triangle

ctx.moveTo(200.100)
ctx.lineTo(200.300)
ctx.lineTo(400.200)
ctx.lineTo(200.100)
ctx.stroke()
Copy the code

You can use your imagination and try to draw a hexagon, a pentagon, etc

1.3 Curve Figure

  • Ctx. arc(x, y, radius, startAngle,endAngle, anticlockwise) : center position is (x, y), radius is radius, startAngle startAngle,endAngle endAngle, draw an arc
  • Anticlockwise: true anticlockwise, false anticlockwise
  • Angle: Degree x math.pi /180

1.3.1 round

  • The hollow circle
ctx.arc(250.250.100.0 ,360 * Math.PI/180);
ctx.stroke();
Copy the code
  • Solid round
ctx.arc(250.250.100.0 ,360 * Math.PI/180);
ctx.fill()
Copy the code
  • Concentric circles

You can try to draw concentric circles, I’m sure you’ll draw them like this:

ctx.arc(250.250.100.0 ,360 * Math.PI/180);
ctx.stroke();
ctx.arc(250.250.50.0 ,360 * Math.PI/180);
ctx.stroke();
Copy the code

If YOU draw it, you’ll see that the two circles are connected:

  • Ctx.beginpath () : Starts a new path
  • Ctx.closepath () : terminates a path
  • These two methods usually come in pairs

Modify code:

// Start a path
ctx.beginPath()
ctx.arc(250.250.100.0 ,360 * Math.PI/180);
ctx.stroke();
// Pick up the pen
ctx.closePath()

// Start a new path
ctx.beginPath()
ctx.arc(250.250.50.0 ,360 * Math.PI/180);
ctx.stroke();
ctx.closePath()
Copy the code

Note: The stroke() and fill() methods only apply to the current path and are invalid when closed!

1.3.1 arc

  • When drawing arcs, you do not need to close the ctx.closepath () path
ctx.beginPath()
ctx.arc(250.250.100.0 ,60 * Math.PI/180);
ctx.stroke();
Copy the code

Shut down the path

There is another way to draw an arc:

  • CTX. ArcTo (cx,cy,x2,y2,radius) :(cx,cy) is the control point and (x2,y2) is the end point of the arc. The reason why there is no starting point is that when you draw an arc this way, the general length of the arc starts with lineTo() or moveTo()

Example:

ctx.moveTo(50.100); 
ctx.lineTo(200.100); // Draw a horizontal line, (200,100) corresponds to the beginning of the arc
ctx.arcTo(250.100.250.200.50) // Control point (250, 100), arc end point (250, 200), radius 50
ctx.lineTo(250.250);
ctx.stroke();
Copy the code

It’s a little bit hard to understand, but you can draw it yourself.

1.4 Bezier curve

Bezier curve, also known as Bezier curve or Bezier curve, is a mathematical curve used in two-dimensional graphics applications. General vector graphics software uses it to accurately draw curves. A Bates curve consists of line segments and nodes, which are draggable fulcrum points, and line segments that are like stretchable rubber bands. The pen tool we see on the drawing tool is used to do this kind of vector. A Bezier curve is a curve controlled by one or more points.

1.4.1 Quadratic Bessel curve

  • CTX. QuadraticCurveTo (Cx, cy, x2, y2) : Similar to arcTo, Bessel curves are also composed of three coordinates: (x1, y1) as the starting point provided by moveTo or lineTo, (Cx, cy) as the control point, and (x2, y2) as the ending point.

Note: Quadratic Bezier curves have only one control point

ctx.moveTo(100.200)
ctx.quadraticCurveTo(150.200.200.300)
ctx.stroke()
Copy the code

Draw a chat bubble

ctx.moveTo(75.25);
ctx.quadraticCurveTo(25.25.25.62);
ctx.quadraticCurveTo(25.100.50.100);
ctx.quadraticCurveTo(50.120.30.125);
ctx.quadraticCurveTo(60.120.65.100);
ctx.quadraticCurveTo(125.100.125.62);
ctx.quadraticCurveTo(125.25.75.25);
ctx.stroke();
Copy the code

1.4.2 Cubic Bezier curves

  • CTX. BezierCurveTo (CX1, CY1, CX2, CY2, X, Y) : A cubic Bezier curve is composed of two control points and end coordinates (x, y). The same starting coordinates are also provided by lineTo and moveTo.

ctx.moveTo(200.100);
ctx.bezierCurveTo(150.50.250.200.200.200);
ctx.stroke();
Copy the code

1.4.3 Quartic Bezier curves

Here is:

2. Canvas line style

lineWidth

  • ctx.lineWidth
ctx.beginPath()
ctx.lineWidth = 2
ctx.moveTo(100.100)
ctx.lineTo(300.100);
ctx.stroke();
ctx.closePath()
ctx.beginPath()
ctx.lineWidth = 10
ctx.moveTo(300.100);
ctx.lineTo(300.200);
ctx.stroke();
ctx.closePath()
Copy the code

2.2 lineCap line style at the beginning and end

  • ctx.lineCap
  • Lines begin and end, not links
  • Ctx. lineCap = ‘butt’ Default value for wireless caps

  • Ctx. lineCap = ’round’

  • Ctx. lineCap = ‘square

Note: Setting a style other than the default here will make the line segment longer by adding a cap to the end of the line segment. The extra length is half the width of the line segment

The style of the lineJoin

  • ctx.lineJoin

  • It’s where the lines join, not where the lines start and end

  • Ctx. lineJoin = ‘miter’ default, sharp corners

  • Ctx. lineJoin = ‘bevel’ bevel

  • Ctx. lineJoin = ’round’ rounded corners

Conclusion:

2.4 setLineDash Sets line false and solid styles

  • Ctx.setlinedash ([solid line width, distance])
ctx.beginPath()
ctx.setLineDash([2.2]);
ctx.moveTo(100.100)
ctx.lineTo(300.100);
ctx.stroke();
ctx.closePath()
Copy the code

3. Text operations

3.1 Text operation method

  • Draw the fillText ctx.filltext (text, x, y, maxWidth)

Exceeding maxWidth is compressed to maxWidth length

ctx.fillText("Ha ha".100.100)
Copy the code
  • Draw strokeText ctx.strokeText(text, x, y, maxWidth)
ctx.strokeText("Ha ha".100.100)
Copy the code
  • Get text length measureText

Get the UI length of the text in px, not the word length of the text

const l = ctx.measureText("Ha ha").width;
console.log(l); / / 20
Copy the code

3.2 Text Attributes

  • ctx.font = “font-style font-weight font-size/line-height font-family”
ctx.font = "bold 100px Kai"
ctx.strokeText("Ha ha".100.100)
Copy the code

  • Text alignment cxt.textalign
ctx.font = "bold 30px 宋体"
ctx.moveTo(100.100)
ctx.lineTo(100.500)
ctx.stroke()
ctx.textAlign = 'start'
ctx.strokeText("Ha ha".100.100)
ctx.textAlign = 'end'
ctx.strokeText("Ha ha".100.150)
ctx.textAlign = 'center'
ctx.strokeText("Ha ha".100.200)
ctx.textAlign = 'left'
ctx.strokeText("Ha ha".100.250)
ctx.textAlign = 'right'
ctx.strokeText("Ha ha".100.300)
Copy the code

  • Text alignment textBaseline

Font is still the most used text manipulation property, and alignment is rarely used

4. Graphical operation

4.1 Basic picture operations

Canvas provides image import through drawImage() method to perform a series of cropping, tiling and other processing on images.

4.1.1 Picture drawing

  • Ctx. drawImage(image, dx, dy) : image is the image element, and (dx, dy) is the coordinate to draw the upper left corner of the image.
const img = new Image();
img.src = '/src/img/aaa.png'
img.onload = () = > {
  ctx.drawImage(img, 100.100);
}
Copy the code

Note: If the image object is created by new, it needs to be done after onLoad

  • Ctx. drawImage(image, dx, dy, dw, dh) : dw and dh are the width and height of the image to draw.
const img = new Image();
img.src = '/src/img/aaa.png'
img.onload = () = > {
  ctx.drawImage(img, 100.100.50.50);
}
Copy the code
  • DrawImage (image, sx, sy, sw, sh, dx, dy, dw, dh) :(sx, sy)

Note: here sx, sy, sw, sh are for the source image, dx, dy, dw, dh are for the drawn image on the canvas

const img = new Image();
img.src = '/src/img/aaa.png'
img.onload = () = > {
  ctx.drawImage(img, 50.50.50.50.0.0.100.100)}Copy the code

The Sprite effect can be achieved by loading the image once and showing different image elements by cropping them in different positions.

4.1.2 Tiling effect

  • ctx.createPattern(element, type)
  • Type The following types are available:
    • Repeat: x, y axis fully tiled
    • Repeat -x: X-axis tile
    • Repeat -y: y tile
    • No-repeat: not tiled

Tiling is usually used with rect and Arc circles. Element can tile images, videos, and even canvas elements.

  • Tile image
const img = new Image();
img.src = '/src/img/aaa.png'
img.onload = () = > {
  ctx.rect(0.0.300.300)
  const pattern = ctx.createPattern(img, 'repeat') as CanvasPattern;
  ctx.fillStyle = pattern;
  ctx.fill();
}
Copy the code

  • Tile canvas
  // Create a new canvas
  const canvas2 = document.createElement('canvas')
  canvas2.width = 50;
  canvas2.height = 50;
  const ctx2 = canvas2.getContext('2d') as CanvasRenderingContext2D;
  ctx2.fillStyle = 'blue'
  ctx2.arc(25.25.25.0.360 * Math.PI/180);
  ctx2.fill()

  ctx.rect(0.0.300.300)
  const pattern = ctx.createPattern(canvas2, 'repeat-x') as CanvasPattern;
  ctx.fillStyle = pattern;
  ctx.fill();
Copy the code

Fill text with pictures

  // Fill the image with text
  const pattern = ctx.createPattern(img, 'repeat') as CanvasPattern
  ctx.font = 'bold 100px Kai'
  ctx.fillStyle = pattern;
  ctx.fillText('CANVAS'.80.100);
Copy the code

4.1.3 Clipping function

The cropping function requires three steps:

  1. Create a cropping template, that is, crop an image into what shape
  2. Call clipping method: cxt.clip()
  3. Cut the picture into the shape of the first step
  // 1. Create a reduction template
  ctx.arc(50.50.50.0.360 * Math.PI/180);
  / / 2. Tailoring
  ctx.clip()
  // 3. Draw cropped image
  ctx.drawImage(img, 0.0);
Copy the code

4.2 Deformation Operation

2 translation

  • Ctx.translate (x, y) : Translate the graph by x, y distance

Note: after a translation, the graph before the translation is not cleared

ctx.beginPath()
ctx.fillRect(0.0.100.100)
setTimeout(() = > {
  ctx.translate(200.200);
  ctx.fillRect(0.0.100.100)},1000)
ctx.closePath()
Copy the code

4.2.2 zoom

  • Ctx.scale (x, y) : Scale the graph along the x and y axes

Note: After scaling, the xy coordinate of the graph is also scaled, and the width of the line is also scaled

ctx.fillRect(200.200.100.100)
ctx.scale(1.5.1.5)
ctx.fillStyle = 'rgba (0, 0, 0, 0.3)'
ctx.fillRect(200.200.100.100)
ctx.closePath()
Copy the code

We can scale x and y to the left using Translate

ctx.fillRect(200.200.100.100)
ctx.translate(-100, -100)
ctx.scale(1.5.1.5)
ctx.fillStyle = 'rgba (0, 0, 0, 0.3)'
ctx.fillRect(200.200.100.100)
ctx.closePath()
Copy the code

Holdings rotation

  • Ctx. rotate(deg) : Rotates a graph by deg degrees

Note: The center of the rotation is the origin of the coordinates

ctx.fillRect(200.200.50.50)
ctx.rotate(10 * Math.PI/180)
ctx.fillRect(200.200.50.50)
Copy the code

  • The center of rotation can be changed using Translate
ctx.fillRect(200.200.50.50)
ctx.translate(225, -95)
ctx.rotate(45 * Math.PI/180)
ctx.fillRect(200.200.50.50)
Copy the code

4.2.4 One-step writing method

  • ctx.transform(a,b,c,d,e,f)
    • A Horizontal scaling
    • B Horizontal inclination
    • C Vertical inclination
    • D Vertical scaling
    • E horizontal movement
    • F Vertical movement

Ctx.transform (1,0,0,1,-100,-100) translate(-100, -100)

4.3 Pixel Operation

  • GetImageData (x, y, W, h) gets image pixel data
  • PutImageData () redraws the pixel data onto the canvas

Note that the image must be drawn on the canvas before getImageData()

  • GetImageData (x, y, w, h) : Get the pixel data of canvas starting from (x, y), width W, height H
const img = new Image()
img.src = '/src/img/aaa.png'
img.onload = () = > {
  ctx.drawImage(img, 0.0);
  console.log(ctx.getImageData(0.0.500.500));
}
Copy the code

In which the data is the data from pixel data, data inside the data corresponding to r, g, b, a four values, once every four cycle: [R2, R1, G1, B1 and A1 G2, B2, A2,… , and RGBA are array structures, respectively

  • PutImageData (pixiList, x, y) : Draws pixel data in (x, y) coordinates
const img = new Image()
img.src = '/src/img/aaa.png'
img.onload = () = > {
  ctx.drawImage(img, 0.0);
  const imageData = ctx.getImageData(0.0.500.500)
  ctx.putImageData(imageData, 100.100)}Copy the code

We can process the data and output it to the canvas again

Note: if you don’t want your browser to get stuck, it’s best not to print data in a for loop!! 😒

4.3.1 Color Flipping (Positive and negative effects)

  • Color flip is to invert R, G, and B, namely: 255-R, 255-g, and 255-b
  for (let i = 0; i < imageData.data.length; i+=4) {
    // Iterate over each pixel
    imageData.data[i] = 255 - imageData.data[i] 
    imageData.data[i+1] = 255 - imageData.data[i+1] 
    imageData.data[i+2] = 255 - imageData.data[i+2] 
  }
  ctx.clearRect(0.0, canvas.width, canvas.height)
  ctx.putImageData(imageData, 0.0)
Copy the code

4.3.2 Black-and-white effect

  • Is to take the average of the RGB three colors, and then set this average for all pixels
  for (let i = 0; i < imageData.data.length; i+=4) {
    // Iterate over each pixel
    const avg = (imageData.data[i] + imageData.data[i+1]  + imageData.data[i+2) /3
    imageData.data[i] = avg
    imageData.data[i+1] = avg
    imageData.data[i+2] = avg
  }
Copy the code

It’s better to use weighted averages

const avg = imageData.data[i] * 0.3 + imageData.data[i] * 0.4 + imageData.data[i] * 0.3
Copy the code

Take weighted average value of RGB respectively to achieve retro effect

imageData.data[i] = imageData.data[i] * 0.39 + imageData.data[i + 1] * 0.76 + imageData.data[i+2] * 0.18
    imageData.data[i+1] = imageData.data[i] * 0.35 + imageData.data[i + 1] * 0.68 + imageData.data[i+2] * 0.16
    imageData.data[i+2] = imageData.data[i] * 0.27 + imageData.data[i + 1] * 0.53 + imageData.data[i+2] * 0.13
Copy the code

4.3.3 Adjust light and Dark

  • Adding a positive number to each RGB value brightens it, and adding a negative number darkens it
  for (let i = 0; i < imageData.data.length; i+=4) {
    const l = 80 / / - 80
    imageData.data[i] += l
    imageData.data[i+1] += l
    imageData.data[i+2] += l
  }
Copy the code

4.3.4 Red, green and blue masks

  • R takes the average value of RGB, GB is 0, and the same applies to other masks
 for (let i = 0; i < imageData.data.length; i+=4) {
    const avg =  (imageData.data[i] + imageData.data[i+1]  + imageData.data[i+2) /3
    imageData.data[i] = avg
    imageData.data[i+1] = 0
    imageData.data[i+2] = 0
  }
Copy the code

4.3.5 Modify transparency

  • The opacity can be modified by multiplying the A value by A decimal
  for (let i = 0; i < imageData.data.length; i+=4) {
    imageData.data[i+3] = imageData.data[i+3] * 0.5
  }
Copy the code

You can think about how to convert a picture into pixel wind, here is an idea for you, you can first reduce the picture, and then enlarge, the specific threshold, you can try.

4.3.6 Creating pixel regions

  • CreateImageData (w, h) creates an area of pixels w wide and h high
  • CreateImageData (imageData) creates a pixel manipulation area the same width and height as imageData

Perform pixel manipulation on a region

  const imageData2 = ctx.createImageData(300.300)
   for (let i = 0; i < 300 * 300 * 4; i+=4) {
    imageData2.data[i] = 100
    imageData2.data[i+1] = 100
    imageData2.data[i+2] = 188
    imageData2.data[i+3] = 255
  }
Copy the code

Gradients and Shadows

5.1 Linear Gradient

  • Ctx.createlineargradient (x1, y1, x2, y2) : Create a gradient object that starts with (x1, y1) and ends with (x2, y2)
  • addColorStop(value, color); Add more than one color to the gradient object created above. Value is the offset of the gradient position (0-1) and color is the gradient color
ctx.rect(0.0, canvas.width, canvas.height)
const gnt = ctx.createLinearGradient(0.0, canvas.width, 0);
gnt.addColorStop(0.1.'red');
gnt.addColorStop(0.2.'green');
gnt.addColorStop(0.3.'blue');
gnt.addColorStop(0.4.'purple');
ctx.fillStyle = gnt
ctx.fill()
Copy the code

  • Text gradient
ctx.font = 'bold 50px Kai'
const gnt = ctx.createLinearGradient(0.0, canvas.width, 0);
gnt.addColorStop(0.'purple');
gnt.addColorStop(1.'#fff');
ctx.fillStyle = gnt
ctx.fillText("Canvas".100.100);
Copy the code

5.2 Radial Gradient

  • CreateRadialGradient (x1,y1,r1,x2,y2,r2) :(x1,y1),r1 is the center coordinate and radius of the gradient at the beginning, (x2,y2),r2 is the center coordinate and radius of the gradient at the end
const gnt = ctx.createRadialGradient(250.250.100.300.300.50);
gnt.addColorStop(0.'red')
gnt.addColorStop(0.5.'blue')
gnt.addColorStop(1.'yellow')
ctx.fillStyle = gnt;
ctx.arc(250.250.100.0.2*Math.PI);
ctx.fill()
Copy the code

5.3 the shadow

  • ShadowOffsetX: right offset of shadow
  • ShadowOffsetY: Downward offset of shadow
  • ShadowColor: shadowColor
  • ShadowBlur: the degree of shadowBlur. The greater the value, the more blurred it is
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowColor = 'red'
ctx.shadowBlur = 5
ctx.fillRect(100.100.100.100)
Copy the code

  • Set a shadow for the text
ctx.font = 'bold 50px Kai'
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowColor = 'red'
ctx.shadowBlur = 5
ctx.fillText("Canvas".100.100);
Copy the code

  • Set a shadow on the image
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowColor = 'red'
ctx.shadowBlur = 5
const img = new Image();
img.src = '/src/img/aaa.png'
img.onload = () = > {
  ctx.drawImage(img, 100.100.100.100)}Copy the code

Note: The code to set the shadow should be written before the drawing code

6 canvas path

In Canvas, all graphs except rectangle are drawn based on path

6.1 Start Path and Close Path

  • BeginPath () and closePath ()

6.1.1 Start Path

Take a look at the following example:

ctx.moveTo(100.100)
ctx.lineTo(300.100)
ctx.strokeStyle = "red"
ctx.stroke()
ctx.moveTo(100.200)
ctx.lineTo(300.200)
ctx.strokeStyle = "blue"
ctx.stroke()
Copy the code

Code we draw two straight lines, the above is red, the following is blue, but we found that the actual picture, purple is above, the following is blue, because the second picture of blue, he set up the blue style will affect the style of the first set of red, is directly to cover above, so the red + blue, the first line becomes purple.

Canvas is drawn according to states. Each drawing will detect all states such as: StrokeStyle, fillStyle, etc., if we just set it once, then the whole program will always use that state, and if we use beginPath() every time, a new path, then they will use their own state.

Change the above code to this:

ctx.moveTo(100.100)
ctx.lineTo(300.100)
ctx.strokeStyle = "red"
ctx.stroke()
ctx.beginPath()
ctx.moveTo(100.200)
ctx.lineTo(300.200)
ctx.strokeStyle = "blue"
ctx.stroke()
Copy the code

Add beginPath so that both lines show the correct color.

Note: The criterion for determining whether a path is the same is whether the beginPath() method is used, not whether there is a visual beginPath

Using the following methods only draws graphics and does not start new paths: moveTo(), lineTo(), strokeRect(), fillRect(), rect(), Arc (), arcTo(), quadricCurveTo() and bezierCurveTo()

6.1.2 Closing a Path

Here are a few concepts:

  • Close path: Links the start and end points of a path to form a closed graph
  • End path: means to start a new path, so to end a path, only beginPath() is used to start a new path.

Look at this example:

ctx.arc(250.250.100.0.Math.PI)
ctx.strokeStyle = 'red'
ctx.stroke();
Copy the code

Currently there is only one path, but we have not closed the path, so the arc is not sealed.Add the closePath ()

ctx.arc(250.250.50.0.Math.PI)
ctx.strokeStyle = 'red'
ctx.closePath()
ctx.stroke();
Copy the code

And then we see that the arc is closed

Note: Close path is written before stroke

Let’s add another blue arc:

ctx.arc(250.250.50.0.Math.PI)
ctx.strokeStyle = 'red'
ctx.closePath()
ctx.stroke();
ctx.arc(150.250.50.0.Math.PI)
ctx.strokeStyle = 'blue'
ctx.closePath()
ctx.stroke();
Copy the code

We can see that the second arc is connected to the first one, and the first red arc has changed color. This is because clothPath is a closed path. It only connects the beginning and end of the graph. And the beginning of the second arc is also the end of the first arc, so they are joined together, to separate and display the correct color, we just add beginPath:

ctx.arc(250.250.50.0.Math.PI)
ctx.strokeStyle = 'red'
ctx.closePath()
ctx.stroke();
ctx.beginPath()
ctx.arc(150.250.50.0.Math.PI)
ctx.strokeStyle = 'blue'
ctx.closePath()
ctx.stroke();
Copy the code

Remember not to confuse closing paths with ending paths!!

6.2 Checking whether a point is in the current path

  • isPointInPath(x, y)
ctx.arc(250.250.50.0.Math.PI)
ctx.closePath()
ctx.stroke()
console.log(ctx.isPointInPath(250.250)); // true
Copy the code

Note: the isPointInPath method does not support fillRect and strokeRect. If you want to use a rectangle, you can use rect() instead

7. State of canvas

Canvas is drawing based on state. If we do not use beginPath to start a new path, then this state is used globally. We can store and restore state through two methods provided by canvas.

7.1 clip () method

Before using the clip() method, draw a base shape. Then call clip(). This base shape becomes the clipping region

Note: fillRect and strokeRect methods cannot be used as clipping regions, rect can be used instead!!

// 1. Draw the basic graph
ctx.rect(100.100.100.100)
// 2. Crop the rectangle as the clipping area
ctx.clip()
// 3. Draw an image outside the rectangle (fill color also works)
const img = new Image()
img.src = '/src/img/man.jpg'
img.onload = () = > {
  ctx.drawImage(img, 0.0, canvas.width, canvas.height)
}
Copy the code

7.2 the save () and restore ()

Think about it, we cut the picture above, if we do not need to cut the picture to draw, this time will also be cut, how to show the picture behind? We just need to save the state before the clipping, and then start a new path to do the clipping operation, we do not need to clipping, we just need to restore the state before the clipping can continue to do the following things.

The current state can be saved by calling the save method, and the saved state can be restored using the Restore method

// 1. Save the unclipped environment
ctx.save();
// 2. Start clipping
ctx.beginPath();
ctx.arc(250.250.100.0.Math.PI * 2);
ctx.stroke();
ctx.clip();
const img = new Image();
img.src = "/src/img/man.jpg";
img.onload = () = > {
  ctx.drawImage(img, 0.0, canvas.width, canvas.height);
  fn()
};
function fn() {
  // 3. After the clipping is complete, restore the previous state and add the graph that does not need clipping
  ctx.restore();
  const img2 = new Image();
  img2.src = "/src/img/aaa.png";
  img2.onload = () = > {
    ctx.drawImage(img2, 100.250.50.50);
  };
}
Copy the code

The process goes something like this:

You can use beginPath to create two different colored lines. You can also use save and restore to create two different colored lines.

8. Canvas some other methods

8.1 toDataURL ()

Save canvas canvas as an image and return base64, which can be downloaded by browser using window.location = ctx.todataURL (“image/ PNG “).

8.2 globalAlpha property

Change global transparency, range 0-1, to write first, for global effect.

8.3 globalCompositeOperation properties

Normally, when drawing a graph on canvas, the back overwrites the front. You can use this attribute to set the overwriting method. There are several attributes below, and you can try the effect by yourself:

9. Event operations

9.1 Encapsulating a Mouse

    window.tools = {};
    window.tools.getMouse = function (element) {
      var mouse = { x: 0.y: 0 };
      element.addEventListener("mousemove".function (e) {
        var x, y;
        var e = e || window.event;
        if (e.pageX || e.pageY) {
          x = e.pageX;
          y = e.pageY;
        } else {
          x =
            e.clientX +
            document.body.scrollLeft +
            document.documentElement.scrollLeft;
          y =
            e.clientY +
            document.body.scrollTop +
            document.documentElement.scrollTop;
        }
        x -= element.offsetLeft;
        y -= element.offsetTop;
        mouse.x = x;
        mouse.y = y;
      });
      return mouse;
    };
Copy the code

Use:

// @ts-ignore
const mouse = window.tools.getMouse(canvas)
canvas.addEventListener('mousemove'.function(){
  console.log(mouse.x, mouse.y);
})
Copy the code

9.2 Keyboard Events

window.addEventListener('keydown'.function(e){
  console.log(e);
})
Copy the code

10. Physics, Mathematics and animation

10.1 animation requestAnimationFrame

We don’t want to use setInterval for animation, setInterval itself is not very good, so we use requestAnimationFrame instead, but it has compatibility, so we’ll write a tool method and use it later.

window.requestAnimationFrame =
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      window.oRequestAnimationFrame ||
      function (callback) {
        return window.setTimeout(callback, 1000 / 60);
      };
Copy the code

Usage:

let x = 0;
(function move(){
  window.requestAnimationFrame(move)
  ctx.clearRect(0.0, canvas.width, canvas.height)
  ctx.beginPath()
  ctx.arc(x, 250.50.0.2*Math.PI)
  ctx.stroke()
  x+=2}) ()Copy the code

10.2 Trigonometric Functions

10.2.1 Definition of trigonometric functions

First, let’s take a look at the six trigonometric functions we use:

  • Sin (theta) : Math. Sin (theta * Math. PI / 180)
  • Cos (theta) : Math. Cos (theta * Math. PI / 180)
  • Tan (theta) : Math. Tan (theta * Math. PI / 180)
  • arcsin(x/R):Math.asin(x/R)*(180/Math.PI)
  • arccos(y/R):Math.acos(x/y)*(180/Math.PI)
  • arctan(x/y):Math.atan(x/y)*(180/Math.PI)

The specific corresponding relationship is shown as follows:

Note that all the angles in JS are radians, i.e. Math.PI = 180 degrees, and you can convert the degree * math. PI/180 in this way

  • Try writing an arrow that moves with the mouse
class Arrow {
  x = 0;
  y = 0;
  ctx: CanvasRenderingContext2D;
  constructor(x: number, y: number, ctx: CanvasRenderingContext2D) {
    this.x = x;
    this.y = y;
    this.ctx = ctx;
    this.drawArrow(0);
  }
  drawArrow(deg: number) {
    this.ctx.save()
    this.ctx.translate(this.x, this.y)
    this.ctx.rotate(deg)
    this.ctx.moveTo(0.0);
    this.ctx.lineTo(-50, -10);
    this.ctx.lineTo(20, -10);
    this.ctx.lineTo(20, -30);
    this.ctx.lineTo(50.0);
    this.ctx.lineTo(20.30);
    this.ctx.lineTo(20.10);
    this.ctx.lineTo(-50.10);
    this.ctx.lineTo(-50, -10);
    this.ctx.fillStyle = 'black'
    this.ctx.fill();
    this.ctx.restore()
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, 2.0.2 * Math.PI);
    this.ctx.fillStyle = 'red'
    this.ctx.fill();
  }
  linePoint(x: number, y: number) {
    this.ctx.beginPath();
    this.ctx.setLineDash([2.2]);
    this.ctx.moveTo(this.x, this.y);
    this.ctx.lineTo(x, y);
    this.ctx.stroke();
  }
  getDeg(x: number, y: number) {
    const deg = Math.atan2(y - this.y, x - this.x) * (180 / Math.PI);
    this.rotate(deg);
  }
  rotate(deg: number) {
    this.drawArrow(deg * Math.PI / 180)}doAnimate(x: number, y: number) {
    this.ctx.clearRect(0.0, canvas.width, canvas.height);
    this.linePoint(x, y);
    this.getDeg(x, y); }}let arrow = new Arrow(100.100, ctx);
// @ts-ignore
const mouse = window.tools.getMouse(canvas);
canvas.addEventListener("mousemove".function () {
  arrow.doAnimate(mouse.x, mouse.y);
});
Copy the code

If you have rotation logic, it is best to draw the image from the top left corner of the screen and use the translate() method to move it to the desired position so that the rotation will not be incorrect

10.2.2 ellipse

The equation of the ellipse is :(x/a)² + (y/b)² = 1, that is, any point (x,y) on the ellipse satisfies this equation

According to the above figure, the coordinates of each point of the ellipse are:

  • x = centerX + Math.cos(angle)*radiusX
  • y = centerY + Math.sin(angle)*radiusY
class Ellips {
  centerX = 0
  centerY = 0
  radiusX = 10
  radiusY = 5
  canvas: HTMLCanvasElement;
  ctx: CanvasRenderingContext2D;
  constructor(canvas: HTMLCanvasElement, cx:number, cy:number, rx:number, ry:number) {
    this.centerX = cx;
    this.centerY = cy;
    this.radiusX = rx;
    this.radiusY = ry;
    this.canvas = canvas;
    this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D
    this.draw()
  }
  draw() {
    this.ctx.beginPath()
    this.ctx.moveTo(this.centerX + this.radiusX, this.centerY)
    for (let deg = 1; deg < 360; deg++) {
      const x = this.centerX + Math.cos(deg * Math.PI/180) *this.radiusX 
      const y = this.centerY + Math.sin(deg * Math.PI/180) *this.radiusY
      this.ctx.lineTo(x, y)
    }
    this.ctx.closePath()
    this.ctx.stroke()
  }
}
new Ellips(canvas, 250.250.100.50)
Copy the code

10.2.3 Waveform motion

  • If the x coordinate of a Canvas object is given as a sine function, it will swing from side to side
const speed = 0.1 / / speed
const swing = 5 // Swing amplitude
function XMove(deg:number) {
  window.requestAnimationFrame(() = > {
    ctx.beginPath()
    ctx.clearRect(0.0, canvas.width, canvas.height)
    ctx.translate(Math.sin(deg) * swing,0)
    ctx.arc(100.250.30.0.2 * Math.PI)
    ctx.stroke()  
    deg+=speed;
    XMove(deg)
  })
}
XMove(0)
Copy the code

  • If the y coordinate of canvas object is given as sine function, and the x coordinate normally increases constant, the sine function trajectory will move
const speed = 0.1
const swing = 5
function XMove(deg:number) {
  window.requestAnimationFrame(() = > {
    ctx.beginPath()
    ctx.clearRect(0.0, canvas.width, canvas.height)
    ctx.translate(1.Math.sin(deg) * swing)
    ctx.arc(30.250.30.0.2 * Math.PI)
    ctx.fill()  
    deg+=speed;
    XMove(deg)
  })
}
XMove(0)
Copy the code

10.3 Accelerated motion

  • Uniformly accelerated motion
let v = 0;
let a = 0.1;
(function XMove() {
  window.requestAnimationFrame(() = > {
    ctx.beginPath()
    ctx.clearRect(0.0, canvas.width, canvas.height)
    ctx.translate(v, 0)
    ctx.arc(40.250.30.0.2 * Math.PI)
    ctx.stroke()  
    v+=a;
    XMove()
  })
})()
Copy the code

You can think about accelerating uniformly and then going back.

Gravity is the acceleration in the y direction, and friction is the acceleration in the opposite direction, so you can multiply a by a decimal.

The basic content of Canvas is about this much, there is no problem to learn the ABOVE API to do some small effects, you can try to study the collision detection, competent children can try to write a simple physics engine.

All the code for the project is here

References:

Canvas TUTORIAL Canvas MDN written by Teacher Mo Zhenjie