preface

Hello everyone, MY name is Lin Sanxin. When I was recruiting for the school, I was asked about canvas many times by the interviewers, but I didn’t know how to do it. Afterwards, I always wanted to find a chance to learn canvas, but I didn’t have the time. Canvas is becoming more and more important in the front end, so I specially wrote 3 small projects, so that you can get started on Canvas in 10 minutes. Yes, I don’t have her in my heart, only you

1. Canvas implements clock rotation

There are several steps to achieve the following:

  • 1. Find canvascenter, drawTable heart, as well asThe table frame
  • 2, getThe current timeAnd draw it in timeHour hand, minute hand, second hand, as well ascalibration
  • 3, use the timer, every secondGet new timeAnd redraw to reach the clockEffect of rotation

1.1 Table center, table frame

Drawing table heart, table frame has two knowledge points:

  • 1. Find canvasCenter position
  • 2, drawingcircular
//html

<canvas id="canvas" width="600" height="600"></canvas>

// js

// set the center point, where 300 becomes the coordinate 0,0
ctx.translate(300.300)
// Arc (center point X, center point Y, radius, start Angle, end Angle)
ctx.arc(0.0.100.0.2 * Math.PI)
ctx.arc(0.0.5.0.2 * Math.PI)
// Execute the line segment operation stroke
ctx.stroke() 
Copy the code

Let’s have a look at the effect, it seems to be wrong, we want to draw two independent circle lines, how to draw two circles together:

The reason is: when the above code is drawn with a circle, it is connected to the drawing, so after the drawing of the great circle, the line has not been cut, then draw the small circle, it will definitely be connected with the big circle and small circle, the solution is:BeginPath, closePath

ctx.translate(300.300) // set the center point, where 300 becomes the coordinate 0,0

/ / draw circle
+ ctx.beginPath()
// Arc (center point X, center point Y, radius, start Angle, end Angle)
  ctx.arc(0.0.100.0.2 * Math.PI)
  ctx.stroke() // Perform the line segment operation
+ ctx.closePath()

/ / small circle
+ ctx.beginPath()
  ctx.arc(0.0.5.0.2 * Math.PI)
  ctx.stroke()
+ ctx.closePath()
Copy the code

1.2 Hour hand, minute hand and second hand

Drawing these three Pointers, there are two points:

  • 1, according to the currentHours, minutes, secondsGo to theComputing Angle
  • 2. Draw on the calculated anglesHour hand, minute hand, second hand

How to draw the line according to the calculated Angle, for example, calculate the current is 3, then the hour hand should start at 12, clockwise rotation 2 * Math.PI / 12 * 3 = 90°, the minute hand and the second hand are the same, but different from the hour hand is the proportion problem, because there are 12 times in the table. The minute hand and the second hand each have 60 copies

Now, there’s a new problem, again, in the example above, I figured out 90 degrees, so how do we draw the hour hand? We can use moveTo and lineTo to draw line segments. For 90 degrees, all we have to do is rotate the X-axis by 90 degrees clockwise, and then we draw this line segment, and we have a pointer to that Angle. But as it says, we’re going to start at 12, and our default x axis is really horizontal, so we’re going to subtract 90 degrees each time we figure out the Angle with the second hand. In case this is a little confusing, let’s use the following diagram to illustrate, again using the 3 points above:

So that gives you the Angle of the 3-point pointer.

For example, now THAT I have finished drawing the hour hand, and I want to draw the minute hand, the X-axis has been deflected when I drew the hour hand. At this time, the X-axis must be restored to its original shape, so that we can continue to draw the minute hand, otherwise the minute hand is not correct. This is where save and restore come in handy. Save pushes the current state of CTX onto the stack. Restore takes the state at the top of the stack and assigns a value to CTX

Now that I know what I said above, I have to draw the scale. The principle of the starting scale is the same as the principle of time and second hand, except that the scale is dead, there is no calculation, just need to draw 60 small scale and 12 large scale

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

ctx.translate(300.300) // set the center point, where 300 becomes the coordinate 0,0
// Save the state
+ ctx.save()

/ / draw circle
ctx.beginPath()
// Arc (center point X, center point Y, radius, start Angle, end Angle)
ctx.arc(0.0.100.0.2 * Math.PI)
ctx.stroke() // Perform the line segment operation
ctx.closePath()

/ / small circle
ctx.beginPath()
ctx.arc(0.0.5.0.2 * Math. PI) CTX. Stroke (CTX). The closePath () -- -- -- -- -- -- -- -- -- -- - new code// Get the current time, minute, second
let time = new Date(a)let hour = time.getHours() % 12
let min = time.getMinutes()
let sec = time.getSeconds()

/ / hour
ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
ctx.beginPath()
// moveTo sets the starting point of the line
ctx.moveTo(-10.0)
// lineTo sets the point through which the line is drawn
ctx.lineTo(40.0)
// Set the line width
ctx.lineWidth = 10
ctx.stroke()
ctx.closePath()
// Restore to the state of the last save
ctx.restore()
// Restore and save again
ctx.save()

/ / the minute hand
ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10.0)
ctx.lineTo(60.0)
ctx.lineWidth = 5
ctx.strokeStyle = 'blue'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()

/ / second hand
ctx.rotate(2 * Math.PI / 60 * sec -  - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10.0)
ctx.lineTo(80.0)
ctx.strokeStyle = 'red'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()

// To draw the scale, it is the same as to draw the second hand, but the scale is dead
ctx.lineWidth = 1
for (let i = 0; i < 60; i++) {
    ctx.rotate(2 * Math.PI / 60)
    ctx.beginPath()
    ctx.moveTo(90.0)
    ctx.lineTo(100.0)
    // ctx.strokeStyle = 'red'
    ctx.stroke()
    ctx.closePath()
}
ctx.restore()
ctx.save()
ctx.lineWidth = 5
for (let i = 0; i < 12; i++) {
    ctx.rotate(2 * Math.PI / 12)
    ctx.beginPath()
    ctx.moveTo(85.0)
    ctx.lineTo(100.0)
    ctx.stroke()
    ctx.closePath()
}

ctx.restore()
Copy the code

The last step is to update the view and make the clock turn. The first thing to think of is definitely the timer setInterval, but pay attention to one problem: every time you update the view, you must clear the canvas of the last time and start to draw a new view, otherwise you will appear the thousandth hand guanyin scene

Attach the final code:

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

setInterval(() = > {
    ctx.save()
    ctx.clearRect(0.0.600.600)
    ctx.translate(300.300) // set the center point, where 300 becomes the coordinate 0,0
    ctx.save()

    / / draw circle
    ctx.beginPath()
    // Arc (center point X, center point Y, radius, start Angle, end Angle)
    ctx.arc(0.0.100.0.2 * Math.PI)
    ctx.stroke() // Perform the line segment operation
    ctx.closePath()

    / / small circle
    ctx.beginPath()
    ctx.arc(0.0.5.0.2 * Math.PI)
    ctx.stroke()
    ctx.closePath()

    // Get the current time, minute, second
    let time = new Date(a)let hour = time.getHours() % 12
    let min = time.getMinutes()
    let sec = time.getSeconds()

    / / hour
    ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
    ctx.beginPath()
    // moveTo sets the starting point of the line
    ctx.moveTo(-10.0)
    // lineTo sets the point through which the line is drawn
    ctx.lineTo(40.0)
    // Set the line width
    ctx.lineWidth = 10
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    / / the minute hand
    ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
    ctx.beginPath()
    ctx.moveTo(-10.0)
    ctx.lineTo(60.0)
    ctx.lineWidth = 5
    ctx.strokeStyle = 'blue'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    / / second hand
    ctx.rotate(2 * Math.PI / 60 * sec - Math.PI / 2)
    ctx.beginPath()
    ctx.moveTo(-10.0)
    ctx.lineTo(80.0)
    ctx.strokeStyle = 'red'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    // To draw the scale, it is the same as to draw the second hand, but the scale is dead
    ctx.lineWidth = 1
    for (let i = 0; i < 60; i++) {
        ctx.rotate(2 * Math.PI / 60)
        ctx.beginPath()
        ctx.moveTo(90.0)
        ctx.lineTo(100.0)
        // ctx.strokeStyle = 'red'
        ctx.stroke()
        ctx.closePath()
    }
    ctx.restore()
    ctx.save()
    ctx.lineWidth = 5
    for (let i = 0; i < 12; i++) {
        ctx.rotate(2 * Math.PI / 12)
        ctx.beginPath()
        ctx.moveTo(85.0)
        ctx.lineTo(100.0)
        // ctx.strokeStyle = 'red'
        ctx.stroke()
        ctx.closePath()
    }

    ctx.restore()
    ctx.restore()
}, 1000)
Copy the code

The effect is very good:

2. Canvas implements scratch cards

When a lot of people have bought prepaid card, understand all understand ah ha, scrape this layer of gray skin with nails, can see the bottom of the answer.

The thinking goes like this:

  • 1. The bottom answer is onedivThe top gray skin is onecanvas.canvasCover it at firstdiv
  • 2, the mouse event, click and move, the mouse through the pathDraw roundOpen, and setglobalCompositeOperationfordestination-outTo make the mouse over the path aretransparent, a transparent, naturally displayed below the answer information.

So the fill method, the fill method, is actually the stroke method, so fill the shape, stroke just draws the border line

// html
<canvas id="canvas" width="400" height="100"></canvas>
<div class="text">Congratulations on your 100W</div>
<style>
        * {
            margin: 0;
            padding: 0;
        }
        .text {
            position: absolute;
            left: 130px;
            top: 35px;
            z-index: -1;
        }
</style>


// js
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

// Fill the color
ctx.fillStyle = 'darkgray'
// fillRect(start X, start Y, end X, end Y)
ctx.fillRect(0.0.400.100)
ctx.fillStyle = '#fff'
// Draw fill text
ctx.fillText('Scratch card'.180.50)

let isDraw = false
canvas.onmousedown = function () {
    isDraw = true
}
canvas.onmousemove = function (e) {
    if(! isDraw)return
    // Calculate the mouse position on the canvas
    const x = e.pageX - canvas.offsetLeft
    const y = e.pageY - canvas.offsetTop
    / / set globalCompositeOperation
    ctx.globalCompositeOperation = 'destination-out'
    / / draw circles
    ctx.arc(x, y, 10.0.2 * Math.PI)
    // Fill the circle
    ctx.fill()
}
canvas.onmouseup = function () {
    isDraw = false
}
Copy the code

The effect is as follows:

3. Canvas implements artboard and save

Framework: Use vue + elementUI

In fact, it is very simple, and the difficulties are as follows:

  • 1, mouse drag draw square and circle
  • 2. Paint a save canvas and overlay it on the next painting
  • 3. Save the picture

First, we only need to calculate the coordinates of the points clicked by the mouse and the current coordinates of the mouse to calculate the rectangle length and width: X-Beginx, Y-beginy, and the Pythagorean Theorem should be used for the circle: Math.sqrt((x – beginX) * (x – beginX) + (y – beginY) * (y – beginY))

Second, use canvas’s getImageData and putImageData methods

Third, the idea is to generate image links on canvas, assign them to a tag with download function, and actively click a tag to download the image

Check out the results:

I will not explain the specific code, it is not difficult to say, as long as the first two projects understand, this project is very easy to understand:

<template>
  <div>
    <div style="margin-bottom: 10px; display: flex; align-items: center">
      <el-button @click="changeType('huabi')" type="primary">The brush</el-button>
      <el-button @click="changeType('rect')" type="success">A square</el-button>
      <el-button
        @click="changeType('arc')"
        type="warning"
        style="margin-right: 10px"
        >Circular < / el - button ><div>Color:</div>
      <el-color-picker v-model="color"></el-color-picker>
      <el-button @click="clear">empty</el-button>
      <el-button @click="saveImg">save</el-button>
    </div>
    <canvas
      id="canvas"
      width="800"
      height="400"
      @mousedown="canvasDown"
      @mousemove="canvasMove"
      @mouseout="canvasUp"
      @mouseup="canvasUp"
    >
    </canvas>
  </div>
</template>

<script>
export default {
  data() {
    return {
      type: "huabi".isDraw: false.canvasDom: null.ctx: null.beginX: 0.beginY: 0.color: "# 000".imageData: null}; },mounted() {
    this.canvasDom = document.getElementById("canvas");
    this.ctx = this.canvasDom.getContext("2d");
  },
  methods: {
    changeType(type) {
      this.type = type;
    },
    canvasDown(e) {
      this.isDraw = true;
      const canvas = this.canvasDom;
      this.beginX = e.pageX - canvas.offsetLeft;
      this.beginY = e.pageY - canvas.offsetTop;
    },
    canvasMove(e) {
      if (!this.isDraw) return;
      const canvas = this.canvasDom;
      const ctx = this.ctx;
      const x = e.pageX - canvas.offsetLeft;
      const y = e.pageY - canvas.offsetTop;
      this[`The ${this.type}Fn`](ctx, x, y);
    },
    canvasUp() {
      this.imageData = this.ctx.getImageData(0.0.800.400);
      this.isDraw = false;
    },
    huabiFn(ctx, x, y) {
      ctx.beginPath();
      ctx.arc(x, y, 5.0.2 * Math.PI);
      ctx.fillStyle = this.color;
      ctx.fill();
      ctx.closePath();
    },
    rectFn(ctx, x, y) {
      const beginX = this.beginX;
      const beginY = this.beginY;
      ctx.clearRect(0.0.800.400);
      this.imageData && ctx.putImageData(this.imageData, 0.0.0.0.800.400);
      ctx.beginPath();
      ctx.strokeStyle = this.color;
      ctx.rect(beginX, beginY, x - beginX, y - beginY);
      ctx.stroke();
      ctx.closePath();
    },
    arcFn(ctx, x, y) {
      const beginX = this.beginX;
      const beginY = this.beginY;
      this.isDraw && ctx.clearRect(0.0.800.400);
      this.imageData && ctx.putImageData(this.imageData, 0.0.0.0.800.400);
      ctx.beginPath();
      ctx.strokeStyle = this.color;
      ctx.arc(
        beginX,
        beginY,
        Math.round(
          Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))
        ),
        0.2 * Math.PI
      );
      ctx.stroke();
      ctx.closePath();
    },
    saveImg() {
      const url = this.canvasDom.toDataURL();
      const a = document.createElement("a");
      a.download = "sunshine";
      a.href = url;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    },
    clear() {
        this.imageData = null
        this.ctx.clearRect(0.0.800.400)}}};</script>

<style lang="scss" scoped>
#canvas {
  border: 1px solid black;
}
</style>
Copy the code

conclusion

For study group, please click here