We’ve covered a lot of the basics in previous articles. We’ve learned how to use the Canvas drawing API to draw graphics and make them move under various functions. However, there are boundaries in the real world. This article will focus on the following two aspects to learn and explain.

  • Environmental border
  • The frictional force

Environmental border

In most cases, a simple rectangle will form a boundary, so let’s start with the simplest example, based on the canvas size boundary.

How do we deal with judging objects to be out of bounds? Generally, there are four ways

  • Remove the object
  • Reset within bounds
  • Occurs at another symmetric position on the boundary
  • Bounce back inside the boundary

Let’s start by removing objects

Remove the object

If objects continue to be created, then it is a good idea to remove them after they cross the boundary, and it will lead to better performance.

When multiple objects are moving, you should store their references in an array and iterate through the array to move them. You can remove elements from an array using the splice() method. For example, place 100 small balls at random locations on the canvas, move at a random speed that does not exceed the maximum speed, and remove the balls when they cross the boundary.

The following code

/* eslint-disable no-param-reassign */
import stats from '.. /common/stats'
import Ball from '.. /common/Ball'

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

const ballNum = 100
const maxSpeedX = 80
const maxSpeedY = 80
const colors = [
  '#81D4FA'.'#64B5F6'.'#42A5F5'.'#2196F3'.'#1E88E5'.'#1976D2'.'#1565C0'.'#0D47A1',]const balls: Ball[] = []

let remainBallsNum = ballNum

if (canvas) {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight

  const context = canvas.getContext('2d')

  if (context) {
    for (let i = 0; i < ballNum; i += 1) {
      const ball = new Ball(20, colors[i % colors.length])
      ball.x = Math.random() * canvas.width
      ball.y = Math.random() * canvas.height
      ball.vx = (Math.random() * 2 - 1) * maxSpeedX
      ball.vy = (Math.random() * 2 - 1) * maxSpeedY
      balls.push(ball)
    }

    let then = 0
    const drawFrame = (time: number) = > {
      stats.begin()
      const timeInSeconds = time / 1000 // Convert milliseconds to seconds
      const deltaTime = timeInSeconds - then
      then = timeInSeconds

      context.clearRect(0.0, canvas.width, canvas.height)

      for (let i = balls.length - 1; i > -1; i -= 1) {
        balls[i].x += balls[i].vx * deltaTime
        balls[i].y += balls[i].vy * deltaTime
        balls[i].draw(context)

        if (
          balls[i].x - balls[i].radius > canvas.width ||
          balls[i].x + balls[i].radius < 0 ||
          balls[i].y - balls[i].radius > canvas.height ||
          balls[i].y + balls[i].radius < 0
        ) {
          balls.splice(i, 1)}}if(remainBallsNum ! == balls.length) { remainBallsNum = balls.lengthconsole.log(`remain balls: ${remainBallsNum}`)
      }

      stats.end()
      window.requestAnimationFrame(drawFrame)
    }
    drawFrame(0)}}Copy the code

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

Note that if you need to use an array to hold the ball, you can use splice to remove the array elements. To figure out whether it’s out of bounds, you have to consider the radius of the small ball.

if (
  balls[i].x - balls[i].radius > canvas.width ||
  balls[i].x + balls[i].radius < 0 ||
  balls[i].y - balls[i].radius > canvas.height ||
  balls[i].y + balls[i].radius < 0
) {
  balls.splice(i, 1)}Copy the code

Because the length of the array has been changed, the traversal needs to be done in reverse, otherwise the subscripts will be confused, and the feedback on the page will be ball flashing.

for (let i = balls.length - 1; i > -1; i -= 1) {... }Copy the code

Reset within bounds

The idea is that when an object moves out of the boundary, we reset its position. In this way, moving objects can be provided continuously, without worrying that too many objects on the canvas will affect the browser speed, because the number of objects is constant.

For example, let’s make an animation of falling snow, and when the snow falls, reset to the top of the screen.

/* eslint-disable no-param-reassign */
import stats from '.. /common/stats'
import Ball from '.. /common/Ball'

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

const ballNum = 100 // Number of elements
const maxSpeedX = 20 // Maximum initial horizontal velocity
const maxSpeedY = 0 // Maximum initial vertical velocity
const gravity = 4 // The unit of gravity acceleration is pixel /s^2

const colors = [
  '#81D4FA'.'#64B5F6'.'#42A5F5'.'#2196F3'.'#1E88E5'.'#1976D2'.'#1565C0'.'#0D47A1',]const balls: Ball[] = []

if (canvas) {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight

  const context = canvas.getContext('2d')

  const initBall = (ball: Ball, firstInit = false) = > {
    ball.radius = Math.random() * 3 + 4
    ball.x = Math.random() * canvas.width
    ball.y = -Math.random() * canvas.height * (firstInit ? 2 : 1)
    ball.vx = (Math.random() * 2 - 1) * maxSpeedX
    ball.vy = Math.random() * maxSpeedY
  }

  if (context) {
    for (let i = 0; i < ballNum; i += 1) {
      const ball = new Ball(20, colors[i % colors.length])
      ball.lineWidth = 0
      initBall(ball, true)
      balls.push(ball)
    }

    let then = 0
    const drawFrame = (time: number) = > {
      stats.begin()
      const timeInSeconds = time / 1000 // Convert milliseconds to seconds
      const deltaTime = timeInSeconds - then
      then = timeInSeconds

      context.clearRect(0.0, canvas.width, canvas.height)

      for (let i = balls.length - 1; i > -1; i -= 1) {
        balls[i].x += balls[i].vx * deltaTime
        balls[i].vy += gravity * deltaTime
        balls[i].y += balls[i].vy * deltaTime
        balls[i].draw(context)

        if (
          balls[i].x - balls[i].radius > canvas.width ||
          balls[i].x + balls[i].radius < 0 ||
          balls[i].y - balls[i].radius > canvas.height
        ) {
          initBall(balls[i])
        }
      }

      stats.end()
      window.requestAnimationFrame(drawFrame)
    }
    drawFrame(0)}}Copy the code

The core code is

if (
  balls[i].x - balls[i].radius > canvas.width ||
  balls[i].x + balls[i].radius < 0 ||
  balls[i].y - balls[i].radius > canvas.height
) {
  initBall(balls[i])
}
Copy the code

Reset it when it’s out of bounds.

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

The above code can be slightly modified to look like a fountain:

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

Occurs at another symmetric position on the boundary

When an element moves from the left side of the screen, it appears on the right side of the screen. If you move it out on the right, it will appear on the left; Same thing up and down.

We use the demo spaceship from the previous chapter, Velocity and Acceleration of Canvas Animation. We modify the code a little so that when it is moved off the canvas, it appears in another symmetric position.

The core modification code is as follows:

const top = 0
const right = canvas.width
const bottom = canvas.height
const left = 0...if (ship.x - ship.width / 2 > right) {
  ship.x = left - ship.width / 2
} else if (ship.x + ship.width / 2 < left) {
  ship.x = right + ship.width / 2
}
if (ship.y - ship.height / 2 > bottom) {
  ship.y = top - ship.height / 2
} else if (ship.y + ship.height / 2 < top) {
  ship.y = bottom + ship.height / 2
}
Copy the code

Results the following

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

Bounce back inside the boundary

What the bounce does is when the element is about to leave the screen, it keeps its position and only changes the direction of its velocity.

/* eslint-disable no-param-reassign */
import stats from '.. /common/stats'
import Ball from '.. /common/Ball'

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

const v0x = 120
const v0y = -100
const gravity = 500 // The unit of gravity acceleration is pixel /s^2
const bounce = -0.8 // Elastic coefficient

if (canvas) {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight

  const context = canvas.getContext('2d')
  const ball = new Ball(20)
  ball.x = canvas.width / 2
  ball.y = canvas.height / 2
  ball.vx = v0x
  ball.vy = v0y
  ball.lineWidth = 0

  if (context) {
    let then = 0
    const drawFrame = (time: number) = > {
      stats.begin()
      const timeInSeconds = time / 1000 // Convert milliseconds to seconds
      const deltaTime = timeInSeconds - then
      then = timeInSeconds
      context.clearRect(0.0, canvas.width, canvas.height)

      ball.x += ball.vx * deltaTime
      ball.vy += gravity * deltaTime
      ball.y += ball.vy * deltaTime

      if (ball.y + ball.radius > canvas.height) {
        ball.y = canvas.height - ball.radius
        ball.vy *= bounce
      }
      if (ball.y - ball.radius < 0) {
        ball.y = ball.radius
        ball.vy *= bounce
      }

      if (ball.x + ball.radius > canvas.width) {
        ball.x = canvas.width - ball.radius
        ball.vx *= bounce
      }
      if (ball.x - ball.radius < 0) {
        ball.x = ball.radius
        ball.vx *= bounce
      }

      ball.draw(context)
      stats.end()
      window.requestAnimationFrame(drawFrame)
    }
    drawFrame(0)}}Copy the code

Its core code is

if (ball.y + ball.radius > canvas.height) {
  ball.y = canvas.height - ball.radius
  ball.vy *= bounce
}
if (ball.y - ball.radius < 0) {
  ball.y = ball.radius
  ball.vy *= bounce
}
if (ball.x + ball.radius > canvas.width) {
  ball.x = canvas.width - ball.radius
  ball.vx *= bounce
}
if (ball.x - ball.radius < 0) {
  ball.x = ball.radius
  ball.vx *= bounce
}
Copy the code

Note that we also reduced the rebound speed a bit to simulate real elastic loss (the bounce variable in the code above).

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

The frictional force

So far we have achieved ideal motion, ignoring the friction in the real world. It can also be called resistance, damping. Now let’s consider the case of damping.

Standard solution of friction

In the figure, if we have vx and vy, we need to figure out the sum of them and the velocity, v, and then we need to keep decreasing this v. We can’t slow down the x axis and the y axis separately, because it might lead to a strange phenomenon that the velocity is zero in one axis, while the velocity is still moving in the other axis.

/* eslint-disable no-param-reassign */
import stats from '.. /common/stats'
import Ball from '.. /common/Ball'

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

const v0x = (Math.random() * 2 - 1) * 100
const v0y = (Math.random() * 2 - 1) * 200
const frictionV = 1 // The deceleration rate caused by friction

if (canvas) {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight

  const context = canvas.getContext('2d')
  const ball = new Ball(20)
  ball.x = canvas.width / 2
  ball.y = canvas.height / 2
  ball.lineWidth = 0
  ball.vx = v0x
  ball.vy = v0y

  if (context) {
    let then = 0
    const drawFrame = (time: number) = > {
      stats.begin()
      const timeInSeconds = time / 1000 // Convert milliseconds to seconds
      const deltaTime = timeInSeconds - then
      then = timeInSeconds
      context.clearRect(0.0, canvas.width, canvas.height)

      let v = Math.sqrt(ball.vx ** 2 + ball.vy ** 2) // Calculate the total speed
      const angle = Math.atan2(ball.vy, ball.vx) // Calculate the Angle

      if (v > frictionV) {
        v -= frictionV // Speed decrease
      } else {
        v = 0
      }

      ball.vx = v * Math.cos(angle) // Recalculate the partial velocity
      ball.vy = v * Math.sin(angle)
      ball.x += ball.vx * deltaTime // Calculate the displacement
      ball.y += ball.vy * deltaTime

      ball.draw(context)
      stats.end()
      window.requestAnimationFrame(drawFrame)
    }
    drawFrame(0)}}Copy the code

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

A simple solution to friction

So this is using the Pythagorean theorem, and a bunch of trigonometric functions. In fact, friction can be directly simulated by multiplying the velocity in the X and y directions by a coefficient less than 1, and the average user will not notice anything wrong.

/* eslint-disable no-param-reassign */
import stats from '.. /common/stats'
import Ball from '.. /common/Ball'

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

const v0x = (Math.random() * 2 - 1) * 100
const v0y = (Math.random() * 2 - 1) * 200
const friction = 0.97 // Coefficient of friction

if (canvas) {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight

  const context = canvas.getContext('2d')
  const ball = new Ball(20)
  ball.x = canvas.width / 2
  ball.y = canvas.height / 2
  ball.lineWidth = 0
  ball.vx = v0x
  ball.vy = v0y

  if (context) {
    let then = 0
    const drawFrame = (time: number) = > {
      stats.begin()
      const timeInSeconds = time / 1000 // Convert milliseconds to seconds
      const deltaTime = timeInSeconds - then
      then = timeInSeconds
      context.clearRect(0.0, canvas.width, canvas.height)

      ball.x += ball.vx * deltaTime
      ball.y += ball.vy * deltaTime
      ball.vx *= friction // Speed decrease
      ball.vy *= friction

      ball.draw(context)
      stats.end()
      window.requestAnimationFrame(drawFrame)
    }
    drawFrame(0)}}Copy the code

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

Spacecraft add some friction

Let’s add a little more friction to the spacecraft example in this chapter

const friction = 0.996. vThrustShip *= frictionCopy the code

The code is simple, increasing the velocity attenuation coefficient of friction, and then attenuating it for propulsion speed.

As follows:

The demo link gaohaoyang. Making. IO/canvas – prac…

Source link github.com/Gaohaoyang/…

conclusion

In this chapter, we learned how to do things when objects collide with boundaries, including removing, resetting, screen wrapping, and bouncing. He also learned to master friction, a simple coefficient that makes motion more realistic.