The effect

Technology matting

  1. canvas
  2. es6
  3. requestAnimationFrame

canvas

Canvas is a canvas label in the Web, which allows us to freely draw corresponding graphics on it through JS.

es6

Part of ES6 grammar is used in the case, please refer to RUan Yifeng’s ES6

requestAnimationFrame

RequestAnimationFrame, an API that needs to be covered separately here.

Usually we want to achieve animation effects in JS, no more than the following ways

  1. Pure CSS means

    1. Transition the transition
    2. Animation animation
  2. JavaScript means

    1. SetInterval timer

      Call a function repeatedly or execute a code segment with a fixed time delay between calls

    2. SetTimeout decelerator

      The **setTimeout()** method sets a timer that executes a function or a specified piece of code after the timer expires

    3. requestAnimationFrame

requestAnimationFrame

Due to the macro task execution mechanism, although we use the timer setInterval, it is inevitable that the program execution time will be inaccurate, which will eventually lead to a slight lag in the animation effect on the user’s perspective.

RequestAnimationFrame does not have this problem, because the average screen refresh rate is 60 times per second, so when we use requestAnimationFrame, it can execute 60 times per second. So we use it for animation, which is generally much smoother.

If we want to perform a continuous animation, we need to continue calling itself in the requestAnimationFrame callback function.

Such as:

  let index = 0;
  function step(timestamp) {
    console.log(index);
    index++;
    window.requestAnimationFrame(step);
  }
  step();
Copy the code

RequestAnimationFrame returns an ID to stop its execution

let id = window.requestAnimationFrame(() = >{
  console.log(1);
});
// It was cancelled
window.cancelAnimationFrame(id);
Copy the code

Initialize the project environment

Setting up basic Labels

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1, minimum - scale = 1, the user - scalable = no"
    />
    <title>index.html</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        padding-top: 200px;
        text-align: center;
      }
      canvas {
        border: 1px solid # 000;
        background-image: url(./images/20200820014508687.jpg);
        background-size: cover;
      }
    </style>
  </head>
  <body>
    <canvas width="600" height="400"> </canvas>
  </body>
</html>
Copy the code

Create three classes

  1. Bounce is used to Bounce balls
  2. Baffle is used to implement the bottom Baffle
  3. The Game controls the ball and the paddle
  // Used to control the ball and baffle
  class Game {}
  / / ball
  class Bounce {}
  / / damper
  class Baffle {}
Copy the code

A ball

Initialize the ball

class Bounce {
  constructor(ctx, option) {
    this.ctx = option.canvas.getContext('2d'); // Get the canvas context
    this.x = option.x; // The x coordinate of the ball
    this.y = option.y; // The y-coordinate of the ball
    this.speedX = option.speedX; // Horizontal speed
    this.speedY = option.speedY; // Vertical speed
    this.radius = option.radius; // Small sphere radius
    this.canvas = option.canvas; // Canvas DOM object}}Copy the code

Paint balls

class Bounce {
  show() {
    this.ctx.fillStyle = 'aqua'; // Set the ball color
    this.ctx.beginPath();// Reroute the path
    this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2); // Draw the sphere
    this.ctx.fill(); // Fill the color}}Copy the code

Set the ball to move automatically

class Bounce {
  move() {
    this.x += this.speedX;
    this.y += this.speedY;
    this.show(); }}Copy the code

Determine when the ball hits the boundary

class Bounce {
  move() {
    this.x += this.speedX;
    this.y += this.speedY;
    // Determine if the boundary is exceeded
    this.crash();
    this.show();
  }
  // Judge up and down out of bounds
  get overRect() {
    return this.y >= this.canvas.height || this.y <= 0;
  }
  // The ball moves backwards when it hits the boundary
  crash() {
    // The judgment level is out of bounds
    if (this.x >= this.canvas.width || this.x <= 0) {
      // Set the opposite direction
      this.speedX = -this.speedX;
    }
    // Judge up and down out of bounds
    if (this.overRect) {
      // Set the opposite direction
      this.speedY = -this.speedY; }}}Copy the code

Let the ball move

<! DOCTYPE html><html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1, minimum - scale = 1, the user - scalable = no"
    />
    <title>index.html</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        padding-top: 200px;
        text-align: center;
      }
      canvas {
        border: 1px solid # 000;
        background-image: url(./images/20200820014508687.jpg);
        background-size: cover;
      }
    </style>
  </head>
  <body>
    <canvas width="600" height="400"> </canvas>
    <script>
      / / ball
      class Bounce {
        constructor(ctx, option) {
          this.ctx = ctx;
          this.x = option.x;
          this.y = option.y;
          this.speedX = option.speedX;
          this.speedY = option.speedY;
          this.radius = option.radius;
          this.canvas = option.canvas;
        }
        move() {
          // Clear old clearRect(x,y, width, height)
          this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height);
          this.x += this.speedX;
          this.y += this.speedY;
          this.crash();
          this.show();
        }
        // out of bounds
        get overRect() {
          return this.y >= this.canvas.height || this.y <= 0;
        }
        crash() {
          if (this.x >= this.canvas.width || this.x <= 0) {
            this.speedX = -this.speedX;
          }
          // The center touches the top and the bottom
          if (this.overRect) {
            this.speedY = -this.speedY; }}show() {
          this.ctx.fillStyle = 'aqua';
          this.ctx.beginPath();
          this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2);
          this.ctx.fill(); }}const canvas = document.querySelector('canvas'); // Get the canvas tag
      const ctx = canvas.getContext('2d'); // Get the canvas context

      // Create a small ball
      const bounce = new Bounce(ctx, {
        x: parseInt(Math.random() * canvas.width - 50), // random x
        y: 0.speedX: 3.speedY: 3.radius: 30,
        canvas,
      });

      // Temporarily use a timer instead
      setInterval(() = > {
        bounce.move();
      }, 16);
    </script>
  </body>
</html>

Copy the code

baffle

Initialize the baffle

class Baffle {
  constructor(ctx, option) {
    this.ctx = ctx;// Canvas context
    this.canvas = option.canvas; // Canvas DOM element
    this.x = option.x; // Block x coordinates
    this.y = option.y; // Block y coordinates
    this.width = option.width; // The width of the baffle
    this.height = option.height; // The height of the baffle
    this.color = option.color; // The color of the baffle}}Copy the code

Depicting the baffle

class Baffle {
  show() {
    const fillStyle = this.ctx.fillStyle; // Cache the current canvas color
    this.ctx.fillStyle = this.color; // Set the canvas color
    this.ctx.fillRect(this.x, this.y, this.width, this.height); // Draw rectangle - baffle
    this.ctx.fillStyle = fillStyle; // Reset the canvas color}}Copy the code

Add keyboard control events

class Baffle {
    // Control the baffle movement through the keyboard
  controlMove() {
    Canvas does not support binding keyboard events
    document.body.addEventListener('keydown'.(e) = > {
      switch (e.key) {
        case 'ArrowLeft':
          this.x -= 30;
          // If the baffle goes beyond the left boundary
          if (this.x < 0) {
            this.x = 0;
          }
          break;
        case 'ArrowRight':
          this.x += 30;
          // If the baffle goes beyond the right boundary
          if (this.x + this.width > this.canvas.width) {
            this.x = this.canvas.width - this.width;
          }
          break;
        default:
          break; }}); }}Copy the code

The render panel

/ / damper
class Baffle {
  constructor(ctx, option) {
    this.ctx = ctx;
    this.canvas = option.canvas;
    this.x = option.x;
    this.y = option.y;
    this.width = option.width;
    this.height = option.height;
    this.color = option.color;

    // Add keyboard events
    this.controlMove();
  }
  show() {
    console.log(this.x, this.y);
    const fillStyle = this.ctx.fillStyle;
    this.ctx.fillStyle = this.color;
    this.ctx.fillRect(this.x, this.y, this.width, this.height);
    this.ctx.fillStyle = fillStyle;
  }
  controlMove() {
    Canvas does not support binding keyboard events
    document.body.addEventListener('keydown'.(e) = > {
      switch (e.key) {
        case 'ArrowLeft':
          this.x -= 30;
          if (this.x < 0) {
            this.x = 0;
          }
          break;
        case 'ArrowRight':
          this.x += 30;
          if (this.x + this.width > this.canvas.width) {
            this.x = this.canvas.width - this.width;
            console.log(this.x);
          }
          break;
        default:
          break; }}); }}const canvas = document.querySelector("canvas");
const ctx = canvas.getContext('2d');
this.baffle = new Baffle(ctx, {
  x: 0.y: canvas.height - 4.width: 100.height: 4,
  canvas,
  color: 'orange'});// Temporarily use the timer
setInterval(() = > {
  ctx.clearRect(0.0, canvas.width, canvas.height);
  this.baffle.show();
}, 16);
Copy the code

To optimize the

Since, to consider adding a Game class for unified control of balls and baffles, we need to package and optimize the code for balls and baffles first.

Complete ball code

class Bounce {
  / / initialization
  constructor(ctx, option) {
    this.ctx = ctx;
    this.x = option.x;
    this.y = option.y;
    this.speedX = option.speedX;
    this.speedY = option.speedY;
    this.radius = option.radius;
    this.canvas = option.canvas;
  }
  // Set ball to move
  move() {
    this.x += this.speedX;
    this.y += this.speedY;
    this.crash();
    this.show();
  }

  // Determine if the bottom is reached
  get isReachBottom() {
    return this.y >= this.canvas.height;
  }
  // out of bounds
  get overRect() {
    return this.y >= this.canvas.height || this.y <= 0;
  }
  // Handle collision redirection
  crash() {
    if (this.x >= this.canvas.width || this.x <= 0) {
      this.speedX = -this.speedX;
    }
    // The center touches the top and the bottom
    if (this.overRect) {
      this.speedY = -this.speedY; }}// Draw the ball
  show() {
    this.ctx.fillStyle = 'aqua';
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2);
    this.ctx.fill(); }}Copy the code

Complete baffle code

/ / damper
class Baffle {
  / / initialization
  constructor(ctx, option) {
    this.ctx = ctx;
    this.canvas = option.canvas;
    this.x = option.x;
    this.y = option.y;
    this.width = option.width;
    this.height = option.height;
    this.color = option.color;

    // Add keyboard control events
    this.controlMove();
  }
  // Draw the baffle
  show() {
    const fillStyle = this.ctx.fillStyle;
    this.ctx.fillStyle = this.color;
    this.ctx.fillRect(this.x, this.y, this.width, this.height);
    this.ctx.fillStyle = fillStyle;
  }
  // Add keyboard control events
  controlMove() {
    Canvas does not support binding keyboard events
    document.body.addEventListener('keydown'.(e) = > {
      switch (e.key) {
        case 'ArrowLeft':
          this.x -= 30;
          if (this.x < 0) {
            this.x = 0;
          }
          break;
        case 'ArrowRight':
          this.x += 30;
          if (this.x + this.width > this.canvas.width) {
            this.x = this.canvas.width - this.width;
          }
          break;
        default:
          break; }}); }}Copy the code

Game class

Build the Game class structure


/ / game class
class Game {
  // constructor
  constructor(el, option){}// Take care of initialization
  init(el, option){}// Empty the canvas
  clear(){}// Add the animation to play continuously
  addInanimationList() {
    this.animationList.push(this.clear.bind(this));

  }
  / / start
  start() {
    this.requestAnimationFrame();
  }
  // Start the animation
  requestAnimationFrame(){}// Check whether the ball and baffle collide
  get isCrashBaffle() {}}Copy the code

Complete game code


class Game {
    // constructor
  constructor(el, option) {
    this.init(el, option);
    this.start();
  }
     // Take care of initialization
  init(el, option) {
    const canvas = document.querySelector(el);// Get the canvas DOM
    canvas.width = option.width; // Set the width
    canvas.height = option.height; // Set the height
    const ctx = canvas.getContext('2d'); // Get the canvas context
    this.canvas = canvas; 
    this.ctx = ctx;
      // Create a small ball
    this.bounce = new Bounce(ctx, {
      x: parseInt(Math.random() * canvas.width - 50),
      y: 0.speedX: 3.speedY: 3.radius: 30,
      canvas,
    });
      // Create a baffle
    this.baffle = new Baffle(ctx, {
      x: 0.y: canvas.height - 4.width: 100.height: 4,
      canvas,
      color: 'orange'});this.animationList = []; // Store the animation to be continuously executed
    this.addInanimationList(); // Statistics adds animations to be played continuously
  }
     // Empty the canvas
  clear() {
    this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height);
  }
    // Add the animation to play continuously
  addInanimationList() {
    this.animationList.push(this.clear.bind(this));
    this.animationList.push(this.bounce.move.bind(this.bounce));
    this.animationList.push(this.baffle.show.bind(this.baffle));
    // Determine the collision
  }
    / / start
  start() {
    this.requestAnimationFrame();
  }
    // Start the animation
  requestAnimationFrame() {
      // If the ball does not touch the baffle and touches the floor
    if (!this.isCrashBaffle && this.bounce.isReachBottom) {
      alert("You lost.");
      location.reload();
    } else {
      this.animationList.forEach((fn) = > fn());
      this.requestAnimationFrameId = window.requestAnimationFrame(() = > {
        this.requestAnimationFrame(); }); }}// Check whether the ball and baffle collide
  get isCrashBaffle() {
    const isBetweenX =
      this.bounce.x + this.bounce.radius >= this.baffle.x &&
      this.bounce.x <= this.baffle.x + this.baffle.width;
    const isBetweenY =
      this.bounce.y + this.bounce.radius >= this.baffle.y;
    returnisBetweenX && isBetweenY; }}Copy the code

Case complete code

<! DOCTYPE html><html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1, minimum - scale = 1, the user - scalable = no"
    />
    <title>index.html</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        padding-top: 200px;
        text-align: center;
      }
      canvas {
        border: 1px solid # 000;
        background-image: url(./images/20200820014508687.jpg);
        background-size: cover;
      }
    </style>
  </head>
  <body>
    <canvas width="600" height="400"> </canvas>
    <script>
      class Game {
        constructor(el, option) {
          this.init(el, option);
          this.start();
        }
        init(el, option) {
          const canvas = document.querySelector(el);
          canvas.width = option.width;
          canvas.height = option.height;
          const ctx = canvas.getContext('2d');
          this.canvas = canvas;
          this.ctx = ctx;
          this.bounce = new Bounce(ctx, {
            x: parseInt(Math.random() * canvas.width - 50),
            y: 0.speedX: 3.speedY: 3.radius: 30,
            canvas,
          });
          this.baffle = new Baffle(ctx, {
            x: 0.y: canvas.height - 4.width: 100.height: 4,
            canvas,
            color: 'orange'});this.requestAnimationFrameId = undefined;
          this.animationList = [];
          this.addInanimationList();
        }
        clear() {
          this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height);
        }
        addInanimationList() {
          this.animationList.push(this.clear.bind(this));
          this.animationList.push(this.bounce.move.bind(this.bounce));
          this.animationList.push(this.baffle.show.bind(this.baffle));
          // Determine the collision
        }
        start() {
          this.requestAnimationFrame();
        }
        requestAnimationFrame() {
          if (!this.isCrashBaffle && this.bounce.isReachBottom) {
            alert('You lost');
            location.reload();
          } else {
            this.animationList.forEach((fn) = > fn());
            this.requestAnimationFrameId = window.requestAnimationFrame(() = > {
              this.requestAnimationFrame(); }); }}get isCrashBaffle() {
          const isBetweenX =
            this.bounce.x + this.bounce.radius >= this.baffle.x &&
            this.bounce.x <= this.baffle.x + this.baffle.width;
          const isBetweenY =
            this.bounce.y + this.bounce.radius >= this.baffle.y;

          returnisBetweenX && isBetweenY; }}/ / ball
      class Bounce {
        constructor(ctx, option) {
          this.ctx = ctx;
          this.x = option.x;
          this.y = option.y;
          this.speedX = option.speedX;
          this.speedY = option.speedY;
          this.radius = option.radius;
          this.canvas = option.canvas;
        }
        move() {
          this.x += this.speedX;
          this.y += this.speedY;
          this.crash();
          this.show();
        }

        get isReachBottom() {
          return this.y >= this.canvas.height;
        }
        // out of bounds
        get overRect() {
          return this.y >= this.canvas.height || this.y <= 0;
        }
        crash() {
          if (this.x >= this.canvas.width || this.x <= 0) {
            this.speedX = -this.speedX;
          }
          // The center touches the top and the bottom
          if (this.overRect) {
            this.speedY = -this.speedY; }}show() {
          this.ctx.fillStyle = 'aqua';
          this.ctx.beginPath();
          this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2);
          this.ctx.fill(); }}/ / damper
      class Baffle {
        constructor(ctx, option) {
          this.ctx = ctx;
          this.canvas = option.canvas;
          this.x = option.x;
          this.y = option.y;
          this.width = option.width;
          this.height = option.height;
          this.color = option.color;

          this.controlMove();
        }
        show() {
          const fillStyle = this.ctx.fillStyle;
          this.ctx.fillStyle = this.color;
          this.ctx.fillRect(this.x, this.y, this.width, this.height);
          this.ctx.fillStyle = fillStyle;
        }
        controlMove() {
          Canvas does not support binding keyboard events
          document.body.addEventListener('keydown'.(e) = > {
            switch (e.key) {
              case 'ArrowLeft':
                this.x -= 30;
                if (this.x < 0) {
                  this.x = 0;
                }
                break;
              case 'ArrowRight':
                this.x += 30;
                if (this.x + this.width > this.canvas.width) {
                  this.x = this.canvas.width - this.width;
                }
                break;
              default:
                break; }}); }}const game = new Game('canvas', { width: 600.height: 400 });
    </script>
  </body>
</html>

Copy the code

other

  1. 10 minutes. – Takes you to H5-backgammon
  2. Canvas generated poster
  3. The code address
  4. Online demo address