preface

Hello, glad you saw Aaron’s first article. Do front-end for a period of time, recently decided to sort out the knowledge of visualization, interested friends can have a look.

Make a bitter emoticon similar to wechat

This issue will introduce the usage of canvas to realize an expression through canvas. Here are the faces of the morning’s jerks, so let’s get started.

What is a canvas

Canvas is a new tag element of HTML5, which is usually used to make some interesting animations or composite images. The most common application in the market is H5 active page, and most of them are implemented with Canvas.

canvas

The canvas

Canvas is also a DOM element, but it should be noted that the width and height of the canvas are controlled by the attributes on the label. If the width and height of CSS are smaller than the attributes on the label, they will be stretched and deformed.

<canvas id="canvas" width="800" height="800"> Sorry, you don't deserve canvas </canvas>Copy the code

The coordinate system

The default coordinate origin of canvas is at the upper left corner of canvas element. The right is the extension direction of X axis, and the downward is the extension direction of Y axis.Similar to the CSS, the control coordinates can be translated, rotate, Scale, and Transform.

  1. Translate: translate the coordinate system. It is equivalent to shifting the origin of the coordinates from 0,0 to dx, dy.
  2. Rotate (Angle) : rotates the coordinate system. This method controls the clockwise rotation of the coordinate system Angle radian.
  3. Scale (sx, SY) : Scale coordinate system. The method controls the horizontal scaling of the coordinate system sx and the vertical scaling of the coordinate system SY.
  4. Transform (A, B, C, D, E, F) : allows scaling, rotating, moving, and tilting the current environment coordinate system, where A: horizontal scaling drawing, B: horizontal tilting drawing, C: vertical tilting drawing, D: vertical scaling drawing, E: horizontal moving drawing, F: vertical moving drawing.
  5. SetTransform (a, B, C, D,e,f): This method resets the current transform matrix to the identity matrix and then runs the transform with the same parameters.

Common drawing API

  1. The circular arc
arc(x, y, r,startAngle, endAngle, // Draw an arc from startAngle to endAngle from (x,y) with radius r as the center of a circle. ArcTo (x1,y1, x2, y2, radius) // Draw arcs according to two control points (x1,y1) and (x2, y2) and radius.Copy the code
  1. The path
BeginPath () creates a new path moveTo(x, y) moves the brush to the position (x, y) closePath() closes the path Stroke () draws the path stroke fill() draws the closed area fillCopy the code
  1. rectangular
FillRect (x,y, width, height) // Void stokeRect(x,y, width, height) ClearRect (x,y, width, height) // Clear the rectangle (x,y) with width and height as transparentCopy the code
  1. Bessel curve

Canvas is composed of arcs, paths, rectangles and Bezier curves.

QuadraticCurveTo (CP1x, CP1Y,x,y) // (CP1x, CP1Y) control point (x,y) end point bezierCurveTo(CP1x, CP1Y, CP2X, CP2Y,x, Y)// control point 1 (CP2x,cp2y) Control point 2 (x,y) end pointCopy the code
  1. gradient
let gradient = ctx.createLinearGradient( x1 ,y1 ,x2 ,y2); // Let gradient = CTX. CreateRadialGradient (x1,y1, R1,x2,y2, R2); AddColorStop (position, color)// Position: between 0 and 1 color: the color of positionCopy the code
  1. The text
StrokeText (text,x, y, maxWidth)) Draws a text border at the position (x,y). FillText (text,x, y, maxWidth) draws a text border at the position (x,y)Copy the code

State save & Restore

As the methods of moving the coordinate system mentioned above translate and fill color fill have recording effects and may affect the next drawing, canvas provides save and restore methods to isolate the possible effects of each drawing.

Save () // Translate or draw restore()Copy the code

The emoji Case

Round face contour

Function face(CTX, options) {const {x, y, r} = this.options ctx.save() ctx.translate(x, y) ctx.arc(0,0, r, 0, Math.PI * 2); ctx.strokeStyle = this.options.color; const radialGradient = ctx.createRadialGradient( -1 * r / 3, - 1 * r / 3, 4 * r / 5 , -1 * r / 3, -1 * r / 3 , R) radialGradient. AddColorStop (0, 'RGB (255255, 0)) radialGradient. AddColorStop (1, 'RGB (255,215,0)') ctx.fillstyle = radialGradient ctx.fill() ctx.stroke(); ctx.restore(); } face(ctx, { x: 100, y: 100, r: 50 })Copy the code

eyes

The realization of the eyes is divided into two steps, first draw the whites of the eyes, and use the quadratic Bezier curve to realize the curve of the corners. The common Canvas2D library also uses this technique to realize the graph with rounded corners. Finally, a black dot in the center completes the eye

function eye(ctx, options) { const radius = options.radius || 4 const width = options.width || 20 const height = options.height || 12 const x = options.x || 0 const y = options.y || 0 ctx.save() ctx.translate(this.options.x ,this.options.y) ctx.strokeStyle = this.options.color ctx.fillStyle = '#fff' ctx.beginPath(); ctx.moveTo(0 + radius, 0) ctx.lineTo(width - radius, 0) ctx.quadraticCurveTo(width ,0, width, 0 + radius) ctx.lineTo(width, height -radius) ctx.quadraticCurveTo(width, height, width - radius, height) ctx.lineTo(0 + radius, height) ctx.quadraticCurveTo(0, height, 0, height -radius) ctx.lineTo(0, 0 + radius) ctx.quadraticCurveTo(0, 0, 0 + radius, 0) ctx.closePath() ctx.fill() ctx.stroke(); ctx.restore() ctx.save() ctx.fillStyle = 'black' ctx.translate(x, y) ctx.beginPath() ctx.arc(width / 2, height / 2, 3, PI * 2) ctx.stroke() ctx.fill() ctx.restore()} eye(CTX, {// left eye x: 50, y: 80}) eye(CTX, {// right eye x: 90, y: 80})Copy the code

The mouth

function mouth(ctx, options) {
  const width = options.size || 40
  const height = options.height || 10
  const x = options.x || 40
  const y = options.y || 10
  ctx.save()
  ctx.translate(x, y)
  ctx.beginPath()
  ctx.moveTo(0, 0)
  ctx.quadraticCurveTo(width / 2, height, size, 0)
  ctx.stroke()
  ctx.restore()
}
mouth(ctx, {
  x: 60,
  y: 118
})
Copy the code

tears

function tear(ctx, options) { const width = options.width || 6 const height = options.height || 20 const radius = options.radius || 4 const  x = options.x || 0 const y = options.y || 0 ctx.save() const gradient = ctx.createLinearGradient(0, 0 , width, Height) gradient. AddColorStop (0, 'rgba (0191255, 1)) gradient. AddColorStop (1, 'rgba(135,206,250,1)') ctx.fillstyle = gradient ctx.translate(this.options.x, this.options.y) ctx.beginPath() ctx.moveTo(0, 0) ctx.lineTo(width, 0) ctx.lineTo(width, height - radius) ctx.quadraticCurveTo(width, height, width - radius, height) ctx.lineTo(0 + radius, height) ctx.quadraticCurveTo(0, height, 0, height -radius) ctx.lineTo(0, 0) CTX. ClosePath (CTX). The fill (CTX), stroke (CTX). The restore ()} eye (CTX, {/ / left tears x: 90, y: 80}) eye (CTX, {/ / right tears x: 90, y: 80 })Copy the code

At this point, a simple expression is achieved.

Simplicity – Abstract the code

In common canvas libraries such as pixi.js and create.js, graphics usually have a base class of display, which is used to control functions such as display and hiding, zooming and moving, and graphic events. Each graph has the draw method, which renders each graph in turn in the main class. Here’s a brief abstraction of the business code above.

Display

// Write some pseudo-code, Class Display {options = {} constructor(options) {object.assign (this.options,) Show (isShow) {this.options.show = isShow} // Graphical event on(eventType, callBack) {}}Copy the code

Face the class

class Face extends Display { constructor(options) { super(options) } draw() { const { x, y, r } = this.options ctx.save() ctx.translate(this.options.x ,this.options.y) ctx.arc(x, y, r, 0, Math.PI * 2); ctx.strokeStyle = this.options.color; const radialGradient = ctx.createRadialGradient(x -1 * r / 3, y - 1 * r / 3, 40, x -1 * r / 3, y -1 * r / 3 , R) radialGradient. AddColorStop (0, 'RGB (255255, 0)) radialGradient. AddColorStop (1, 'RGB (255,215,0)') ctx.fillstyle = radialGradient ctx.fill() ctx.stroke(); ctx.restore(); }}Copy the code

Eye class

class Eye extends Display {
  constructor(options) {
    super(options)
  }

  draw() {
    const radius = 4
    const width = 20
    const height = 12
    ctx.save()
    ctx.translate(this.options.x ,this.options.y)
    ctx.strokeStyle = this.options.color
    ctx.fillStyle = '#fff'
    ctx.beginPath();
    ctx.moveTo(0 + radius, 0)
    ctx.lineTo(width - radius, 0)
    ctx.quadraticCurveTo(width ,0, width,  0 + radius)
    ctx.lineTo(width, height -radius)
    ctx.quadraticCurveTo(width, height, width - radius, height)
    ctx.lineTo(0 + radius, height)
    ctx.quadraticCurveTo(0, height, 0, height -radius)
    ctx.lineTo(0, 0 + radius)
    ctx.quadraticCurveTo(0, 0, 0 + radius, 0)
    ctx.closePath()
    ctx.fill()
    ctx.stroke();
    ctx.restore()

    ctx.save()
    ctx.fillStyle = 'black'
    ctx.translate(this.options.x ,this.options.y)
    ctx.beginPath()
    ctx.arc(width / 2, height / 2, 3, 0, Math.PI * 2)
    ctx.stroke()
    ctx.fill()
    ctx.restore()
  }
}
Copy the code

Mouth

class Mouth extends Display {
  constructor(options) {
    super(options)
  }
  draw() {
    const size = 40
    const height = 10
    ctx.save()
    ctx.translate(this.options.x, this.options.y)
    ctx.beginPath()
    ctx.moveTo(0, 0)
    ctx.quadraticCurveTo(size / 2, height, size, 0)
    ctx.stroke()
    ctx.restore()
  }
}
Copy the code
Tear class
class Tear extends Display{ constructor(options) { super(options) } draw() { const { width , height, radius } = this.options ctx.save() const gradient = ctx.createLinearGradient(0, 0 , width, Height) gradient. AddColorStop (0, 'rgba (0191255, 1)) gradient. AddColorStop (1, 'rgba(135,206,250,1)') ctx.fillstyle = gradient ctx.translate(this.options.x, this.options.y) ctx.beginPath() ctx.moveTo(0, 0) ctx.lineTo(width, 0) ctx.lineTo(width, height - radius) ctx.quadraticCurveTo(width, height, width - radius, height) ctx.lineTo(0 + radius, height) ctx.quadraticCurveTo(0, height, 0, height -radius) ctx.lineTo(0, 0) ctx.closePath() ctx.fill() ctx.stroke() ctx.restore() } }Copy the code

Main render code

Let face = new face ({x: 100, y: 100, r: 50, color: 'rgba(0,0,0,0.2)'}) const eye1 = new Eye({x: 50, y: 80, color: new Eye) 'rgba(0,0,0,1)'}) const eye2 = new Eye({x: 90, y: 80, color: 'rgba(0,0,0,1)'}) const tearDefault = {width: 6, height: 20, radius: 4 } const tear1 = new Tear({ x: 57, y: 92, ... tearDefault }) const tear2 = new Tear({ x: 98, y: 92, ... tearDefault }) const mouth = new Mouse({ x: 60, y: 118, color: 'rgba(0,0,0,1)'}) const graphics.push(face) graphics.push(eye1) graphics.push(eye2) graphics.push(mouth) graphics.push(tear1) graphics.push(tear2) function render() { graphics.forEach(graphic => { graphic.draw() }) } render()Copy the code

The last

This issue shares a simple canvas case, want to control the length, without adding animation, here to add, with tween.js can easily achieve the above expression tears effect oh, interested friends can practice by themselves. The code address

Next update learn pixi.js from League of Legends