First on the renderings, I believe you have seen this animation in many places. It might look a little different, but it’s basically the same. I don’t know who the original author is, looking at it or pretty cool, no more words, let’s start.

Set up the environment

Develop in TypeScript (your own TS exercise project). As a development server, live-server keeps everything simple.

Those of you who don’t know TS don’t have to worry. Ts code is minimal. Does not affect reading.

Create a project folder and go to the Open command line.

Installing dependency packages

Compiler and development server for TS (with automatic refresh capability). Direct global installation.

npm i -g live-server typescript
Copy the code

Initialize the

tsc --init
npm init
Copy the code

The directory structure

The configuration file

package.json

{
  "devDependencies": {},
  "scripts": {
    "dev": "live-server ./dist | tsc -watch"}}Copy the code

tsconfig.json

{
  "compilerOptions": {
    "incremental": true."target": "es5"."module": "commonjs"."lib": [
      "es2020"."dom"]."outDir": "./dist"."rootDir": "./src"."strict": true."esModuleInterop": true}}Copy the code

Start the project

Automatically opens the browser upon startup

npm run dev
Copy the code

Begin to write

After starting the project. The index.ts file under SRC is compiled directly into the dist folder after the code is saved. The page also refreshes automatically

Page structure


      
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>document</title>
    <style>
      body.html {
        height: 100%;
      }
      body {
        margin: 0;
        background: #eee;
      }
      canvas {
        height: 100%;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <canvas id="cvs"></canvas>
  </body>
  <script src="./index.js"></script>
</html>
Copy the code

The first step is to make the canvas full screen

This is easy. There’s nothing to talk about.

The size of the canvas is determined by the attributes on the tag (width/height). The CSS width and height determines the display size of the element. Similar to a 1920*1080 image you want it to look the same in a 100*100 , so it’s best to have the CSS size consistent with the property width/height

/// <reference path="./index.d.ts" />
let cvs = document.getElementById("cvs");
// CVS is set to type HTMLCanvasElement here with type protection
if (cvs instanceof HTMLCanvasElement) {
  const ctx = cvs.getContext("2d")! ;// Canvas size
  let width: number = 0;
  let height: number = 0;
  // Set the canvas size to be the same as the window size
  const setSize = (a)= > {
    // Get the current document content area width and height
    width = document.documentElement.clientWidth;
    height = document.documentElement.clientHeight;
    // Type protection only works in the last scope, so write it again
    if (cvs instanceof HTMLCanvasElement) {
      // Set the actual canvas width and heightcvs.width = width; cvs.height = height; }};window.onresize = setSize;
  setSize();
}
Copy the code

The second step generates a specified number of balls

The ball has its own x and y coordinates and its own acceleration and its own radius, properties that are needed for later drawing

// Number of balls
const dotNum = 50;
// List of small balls
const dotList: Array<TDot> = [];
/ / random number
const random: TRandom = (min, max) = >
  Math.floor(Math.random() * (max - min + 1) + min);
// Randomly generate 1 or -1
const randomSign = (a)= > [- 1.1][random(0.1)];
for (let i = 0; i < dotNum; i++) {
  dotList.push({
    // random coordinates (4 is radius of circle)
    x: random(4, width - 4),
    y: random(4, height - 4),
    // randomSign is used to make the acceleration have positive and negative values, so the ball has different directions.
    xa: Math.random() * randomSign(),
    ya: Math.random() * randomSign(),
    // Dot radius
    radius: 4
  });
}
Copy the code

Step three: Get the ball moving

Here we use requestAnimationFrame, which receives a callback function and calls it before the next frame on the page is refreshed. This API is the most common way to animate.

Note here that each frame should be drawn before the previous painting of the screen. Use clearRect(), and call beginPath() every time you draw a path to start a new path

// Draw the function
const draw = (a)= > {
  // Empty the last drawing
  ctx.clearRect(0.0, width, height);
  dotList.forEach((dot, index) = > {
    // Calculate the coordinates of the next frame
    dot.x += dot.xa;
    dot.y += dot.ya;
    // Set the color of the ball
    ctx.fillStyle = "#6cf";
    // Draw the ball path
    ctx.beginPath();
    ctx.arc(dot.x, dot.y, dot.radius, 0.Math.PI * 2);
    // Fill the color
    ctx.fill();
  });
  requestAnimationFrame(draw);
};
// Start animation
draw();
Copy the code

The ball moves in its own speed and direction. But soon you’ll be out of sight

Step 4 Add boundary detection

Modify the draw function to calculate the maximum and minimum values of ball coordinates using the width and height of canvas to perform boundary detection and add rebound effects

const draw = (a)= > {
  // Empty the last drawing
  ctx.clearRect(0.0, width, height);
  dotList.forEach((dot, index) = > {
    // Calculate the coordinates of the next frame
    dot.x += dot.xa;
    dot.y += dot.ya;
    // Compute the boundary value
    const Xmin = dot.radius;
    const Xmax = width - dot.radius;
    const Ymin = dot.radius;
    const Ymax = height - dot.radius;
    // Determine whether the coordinates of the next frame are out of bounds. If the coordinates of the next frame are out of bounds, the acceleration is reversed and the ball can bounce off the edge.
    (dot.x >= Xmax || dot.x <= Xmin) && (dot.xa = -dot.xa);
    (dot.y >= Ymax || dot.y <= Ymin) && (dot.ya = -dot.ya);
    // Set the boundary value as the boundary value.
    dot.x = dot.x >= Xmax ? Xmax : dot.x <= Xmin ? Xmin : dot.x;
    dot.y = dot.y >= Ymax ? Ymax : dot.y <= Ymin ? Ymin : dot.y;
    // Set the color of the ball
    ctx.fillStyle = "#6cf";
    // Draw the ball path
    ctx.beginPath();
    ctx.arc(dot.x, dot.y, dot.radius, 0.Math.PI * 2);
    // Fill the color
    ctx.fill();
  });
  requestAnimationFrame(draw);
};
Copy the code

rendering

Step 5 Connect the balls

Rules for designing connections:

  • A line is drawn between two balls that are less than the specified distance
  • Line thickness and transparency become coarser and less transparent as distance increases

To compute the distance of all the balls in pairs, you simply compute the subsequent balls with the current one in each dotList traversal, so that you can count all the balls every two times in a single drawing.

The distance is calculated using a simple Pythagorean theorem, as shown here, a²+b²=c²

Add the distSquare variable and modify the draw function. The ball code is placed last to prevent lines from being drawn on top of the ball

// Default distance (square)
const distSquare = 10000;
// Draw the function
const draw = (a)= > {
  // Empty the last drawing
  ctx.clearRect(0.0, width, height);
  dotList.forEach((dot, index) = > {
    / * *... Omit some code **/
    // Wires between the balls
    for (let i = index + 1; i < dotList.length; i++) {
      // Dot
      let nextDot = dotList[i];
      // Calculate the difference between the x and y coordinates of the two balls
      let x_dist = dot.x - nextDot.x;
      let y_dist = dot.y - nextDot.y;
      // Calculate the slash length
      let dist = x_dist * x_dist + y_dist * y_dist;
      // If the distance between two points is less than the preset value, connect the two points
      if(dist < distSquare) { drawLine(dist, dot, nextDot); }}// Set the color of the ball
    ctx.fillStyle = "#6cf";
    // Draw the ball path
    ctx.beginPath();
    ctx.arc(dot.x, dot.y, dot.radius, 0.Math.PI * 2);
    // Fill the color
    ctx.fill();
  });
  requestAnimationFrame(draw);
};
Copy the code

Implement line function

// Connect two balls (parameters: distance between two points (square value), current ball, next ball)
const drawLine: TDrawLine = (dist, dot, nextDot) = > {
  // The ratio of the distance difference to the preset distance calculates transparency. The closer the distance, the more opaque it becomes
  let op = (distSquare - dist) / distSquare;
  // Calculate the line width
  const lineWidth = op / 2;
  ctx.lineWidth = lineWidth; 
  // Set the line color and transparency
  ctx.strokeStyle = `rgba(20, 112, 204,The ${op + 0.2}) `; 
  / / path
  ctx.beginPath();
  ctx.moveTo(dot.x, dot.y);
  ctx.lineTo(nextDot.x, nextDot.y);
  Draw a line / /
  ctx.stroke();
};
Copy the code

Pictured above,

Step 6 Mouse follow effect

First get the mouse coordinates in real time

// Mouse coordinates (-1 means not in window)
let point: Point = { x: - 1, y: - 1 };
// Get mouse coordinates in real time
window.addEventListener("mousemove".({ clientX, clientY }) = > {
  point = { x: clientX, y: clientY };
});
// Remove window coordinates clear
window.addEventListener("mouseout".(a)= > {
  point = { x: - 1, y: - 1 };
});
Copy the code

Then modify the draw function to include mouse connections and range following.

// Draw the function
const draw = (a)= > {
  // Empty the last drawing
  ctx.clearRect(0.0, width, height);
  dotList.forEach((dot, index) = > {
    / * *...... Omit some code **/

    // Connect the ball to the mouse (not -1 means the mouse is inside)
    if(point.x ! = =- 1) {
      // Calculate the difference between the mouse and the current ball
      let x_dist = point.x - dot.x;
      let y_dist = point.y - dot.y;
      // Calculate the linear distance between the mouse and the current ball
      let dist = x_dist * x_dist + y_dist * y_dist;
      // The value is smaller than the default value.
      if (dist < distSquare) {
        // More than or equal to half of the preset value less than the preset value (the range is an outer circle) accelerate to the mouse
        if (dist >= distSquare / 2) {
          dot.x += 0.02 * x_dist;
          dot.y += 0.02* y_dist; } drawLine(dist, dot, point); }}// Set the color of the ball
    ctx.fillStyle = "#6cf"; // Draw the ball path
    ctx.beginPath();
    ctx.arc(dot.x, dot.y, dot.radius, 0.Math.PI * 2); // Fill the color
    ctx.fill();
  });
  requestAnimationFrame(draw);
};
Copy the code

The trickier part here is the code for accelerating the ball towards the mouse

// The value is smaller than the default value.
if (dist < distSquare) {
  // More than or equal to half of the preset value less than the preset value (the range is an outer circle) accelerate to the mouse
  if (dist >= distSquare / 2) {
    dot.x += 0.02 * x_dist;
    dot.y += 0.02 * y_dist;
  }
  drawLine(dist, dot, point);
}
Copy the code

The innermost judgment is when the ball coordinates are in the outer mouse circle. Add two percent of the difference between the ball’s coordinates and the mouse’s, and the ball will go significantly faster.

And why is it toward the mouse:

When the ball is to the left of the mouse, the coordinate difference is positive, accelerating to the right.

When the ball is to the right of the mouse, the coordinate difference is negative and the ball accelerates to the left. Same thing up and down.

And the ball coordinates plus the value is the percentage of the difference. So the orientation is the mouse.

The function is now complete.

The complete code

index.ts

/// <reference path="./index.d.ts" />

let cvs = document.getElementById("cvs");

// CVS is set to type HTMLCanvasElement here with type protection
if (cvs instanceof HTMLCanvasElement) {
  const ctx = cvs.getContext("2d")! ;// Canvas size
  let width: number = 0;
  let height: number = 0;

  // Set the canvas size to be the same as the window size
  const setSize = (a)= > {
    // Get the current document content area width and height
    width = document.documentElement.clientWidth;
    height = document.documentElement.clientHeight;
    // Type protection only works in the last scope, so write it again
    if (cvs instanceof HTMLCanvasElement) {
      // Set the actual canvas width and heightcvs.width = width; cvs.height = height; }};window.onresize = setSize;
  setSize();

  // Number of balls
  const dotNum = 50;
  // List of small balls
  const dotList: Array<TDot> = [];
  / / random number
  const random: TRandom = (min, max) = >
    Math.floor(Math.random() * (max - min + 1) + min);
  // Randomly generate 1 and -1
  const randomSign = (a)= > [- 1.1][random(0.1)];

  for (let i = 0; i < dotNum; i++) {
    dotList.push({
      // random coordinates (4 is radius of circle)
      x: random(4, width - 4),
      y: random(4, height - 4),
      // randomSign is used to make the acceleration have positive and negative values, so the ball has different directions.
      xa: Math.random() * randomSign(),
      ya: Math.random() * randomSign(),
      // Dot radius
      radius: 4
    });
  }

  // Mouse coordinates
  let point: Point = {
    x: - 1,
    y: - 1
  };

  // Get mouse coordinates in real time
  window.addEventListener("mousemove".({ clientX, clientY }) = > {
    point = {
      x: clientX,
      y: clientY
    };
  });
  // Remove window coordinates clear
  window.addEventListener("mouseout".(a)= > {
    point = {
      x: - 1,
      y: - 1
    };
  });

  // Default distance value (square value)
  const distSquare = 10000;
  // Connect two balls (parameters: distance between two points (square value), current ball, next ball)
  const drawLine: TDrawLine = (dist, dot, nextDot) = > {
    // The ratio of the distance difference to the preset distance calculates transparency. The closer the distance, the more opaque it becomes
    let op = (distSquare - dist) / distSquare;
    // Calculate the line width
    const lineWidth = op / 2;
    ctx.lineWidth = lineWidth;
    // Set the line color and transparency
    ctx.strokeStyle = `rgba(20, 112, 204,The ${op + 0.2}) `;
    / / path
    ctx.beginPath();
    ctx.moveTo(dot.x, dot.y);
    ctx.lineTo(nextDot.x, nextDot.y);
    Draw a line / /
    ctx.stroke();
  };
  // Draw the function
  const draw = (a)= > {
    // Empty the last drawing
    ctx.clearRect(0.0, width, height);

    dotList.forEach((dot, index) = > {
      // Calculate the coordinates of the next frame
      dot.x += dot.xa;
      dot.y += dot.ya;

      // Compute the boundary value
      const Xmin = dot.radius;
      const Xmax = width - dot.radius;
      const Ymin = dot.radius;
      const Ymax = height - dot.radius;

      // Determine whether the coordinates of the next frame are out of bounds. If the coordinates of the next frame are out of bounds, the acceleration is reversed and the ball can bounce off the edge.
      (dot.x >= Xmax || dot.x <= Xmin) && (dot.xa = -dot.xa);
      (dot.y >= Ymax || dot.y <= Ymin) && (dot.ya = -dot.ya);

      // Correct the out-of-bounds coordinates
      dot.x = dot.x >= Xmax ? Xmax : dot.x <= Xmin ? Xmin : dot.x;
      dot.y = dot.y >= Ymax ? Ymax : dot.y <= Ymin ? Ymin : dot.y;

      // Wires between the balls
      for (let i = index + 1; i < dotList.length; i++) {
        // Dot
        let nextDot = dotList[i];
        // Calculate the difference between the x and y coordinates of the two balls
        let x_dist = dot.x - nextDot.x;
        let y_dist = dot.y - nextDot.y;
        // Use the trigonometric function to calculate the length of the oblique line, i.e. the distance between the two balls
        let dist = x_dist * x_dist + y_dist * y_dist;
        // If the distance between two points is less than the preset value, connect the two points
        if(dist < distSquare) { drawLine(dist, dot, nextDot); }}// Connect the ball to the mouse (not -1 means the mouse is inside)
      if(point.x ! = =- 1) {
        // Calculate the difference between the mouse and the current ball
        let x_dist = point.x - dot.x;
        let y_dist = point.y - dot.y;
        // Calculate the linear distance between the mouse and the current ball
        let dist = x_dist * x_dist + y_dist * y_dist;
        // The value is smaller than the default value.
        if (dist < distSquare) {
          // More than or equal to half of the preset value less than the preset value (the range is an outer circle) accelerate to the mouse
          if (dist >= distSquare / 2) {
            dot.x += 0.02 * x_dist;
            dot.y += 0.02* y_dist; } drawLine(dist, dot, point); }}// Set the color of the ball
      ctx.fillStyle = "#6cf";
      // Draw the ball path
      ctx.beginPath();
      ctx.arc(dot.x, dot.y, dot.radius, 0.Math.PI * 2);
      // Fill the color
      ctx.fill();
    });
    requestAnimationFrame(draw);
  };

  // Start animation
  draw();
}
Copy the code

index.d.ts

interface Point {
  x: number;
  y: number;
}

interface TDot extends Point {
  radius: number;
  xa: number;
  ya: number;
}

type TRandom = (min: number, max: number) = > number;

type TDrawLine = (dist: number, dot: TDot, nextDot: Point) = > void;
Copy the code

The end. Write to share for the first time, deficiencies are many corrections!