Previous articles have mentioned the difficulties and general rules of Canvas animation. Canvas adopts a mode called Immediate render mode, after calling the drawing method, canvas will draw the graph directly to the canvas, and then leave it alone, canvas will not remember any graph you draw. Unlike the DOM model, references to DOM elements are always available in memory. Because of this, we clean the canvas and redraw it before each frame of the animation. The drawing steps are as follows:

  1. Initializes the state of the graph, such as coordinates, size, speed, direction of motion, etc
  2. Clear the canvas and call the drawing function according to the state of the graph
  3. To update the graph status, return to Step 2

In this article we do a particle animation effect to see:

Demand analysis

As can be seen from the figure, the particle motion effect is based on the following rules:

  1. The canvas is made up of a fixed number of particles and lines, and you start with n particles at random coordinates
  2. The particles travel at a fixed speed in random directions and bounce back when they touch the edge of the canvas
  3. When the mouse moves over the canvas, a particle is generated in the mouse position
  4. When the straight-line distance between two particles is less than a certain distance, the connection is not connected or cancelled

Implementation analysis (without code)

  1. To record particle states such as coordinates, velocities, directions, and lines, and encapsulate drawing methods, we need a particle class. To draw n particles, we need an array of particle objects. First, the particle class is initialized, given random coordinates and given initial velocity and initial direction.
  2. Appends a mouse particle object to the particle array with the current mouse position, listens for the mouse slide and updates the mouse particle coordinates.
  3. Each frame we will update the coordinates of the particle object by speed and direction and re-draw the cloth. When monitoring the particle coordinates outside the canvas, we will give the particle class an opposite direction.
  4. When two particles are detected to be less than a certain distance, the line is connected.

Code reading

The first is the particle class, which takes a render context as a parameter and encapsulates the state and drawing method of the particle.

class Particle {
  ctx: CanvasRenderingContext2D;
  x: number;
  y: number;
  vx: number;
  vy: number;
  constructor(ctx: CanvasRenderingContext2D) {
    this.ctx = ctx;
    this.x = random(0, CANVAS_WIDTH);
    this.y = random(0, CANVAS_HEIGHT);
    this.vx = random(-VELOCITY, VELOCITY);
    this.vy = random(-VELOCITY, VELOCITY);
  }
  draw() {
    if (this.x > CANVAS_WIDTH || this.x < 0) {
      this.vx = -this.vx;
    }
    if (this.y > CANVAS_HEIGHT || this.y < 0) {
      this.vy = -this.vy;
    }
    this.x += this.vx;
    this.y += this.vy;
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, PARTICLE_RADIUS, 0.Math.PI * 2);
    // this.ctx.closePath();
    this.ctx.fill(); }}Copy the code

The second is the mouse particle, which takes the render context and dom container as parameters, the same as the particle class, and listens for the mouse position of the container to update its coordinates.

class MouseParticle {
  ctx: CanvasRenderingContext2D;
  x = - 1;
  y = - 1;
  / / the singleton
  static instance: MouseParticle;
  constructor(ctx: CanvasRenderingContext2D, container: HTMLCanvasElement) {
    this.ctx = ctx;
    if (MouseParticle.instance) return MouseParticle.instance;
    container.addEventListener("mouseenter", e => {
      this.x = e.clientX;
      this.y = e.clientY;
    });
    container.addEventListener("mousemove", e => {
      this.x = e.clientX;
      this.y = e.clientY;
    });
    container.addEventListener("mouseleave", e => {
      // Move out of canvas
      this.x = - 1;
      this.y = - 1;
    });
    MouseParticle.instance = this;
    return this;
  }
  draw() {
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, PARTICLE_RADIUS, 0.Math.PI * 2);
    this.ctx.fill(); }}Copy the code

Next we initialize the particle and mouse particle objects and save them into the particle array.

const particles: (Particle | MouseParticle)[] = [];
for (let index = 0; index < PARTICLE_COUNT; index++) {
  const particle = new Particle(ctx);
  particles.push(particle);
}
const mouseParticle = new MouseParticle(ctx, canvas);
Copy the code

Next comes our main drawing function, which is called every frame to redraw the cloth according to the state of the particles.

function draw(ctx: CanvasRenderingContext2D) {
  requestAnimationFrame((a)= > draw(ctx));
  // Clear the canvas and redraw the particles
  ctx.clearRect(0.0, CANVAS_WIDTH, CANVAS_HEIGHT);
  particles.forEach(particle= > {
    particle.draw();
  });
  / / to redraw lines
  ctx.save();
  particles.forEach(source= > {
    particles.forEach(target= > {
      const xDistance = Math.abs(source.x - target.x);
      const yDistance = Math.abs(source.y - target.y);
      const dis = Math.round(
        Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2)));if(dis < PARTICLE_DISTANCE) { ctx.globalAlpha = dis / PARTICLE_DISTANCE; ctx.beginPath(); ctx.moveTo(source.x, source.y); ctx.lineTo(target.x, target.y); ctx.closePath(); ctx.stroke(); }}); }); }Copy the code

In the main drawing function we walk through the array of particles, connecting when two particles are less than a certain distance apart.

The full code is here

Now we have a particle effect


I am writing a series of data Visualization tutorials and experiences, which you can read in the following ways.

  • Github
  • The Denver nuggets
  • Some visual demos