Featured image of post HTML5 Canvas 实现带重力效果的碰撞小球

HTML5 Canvas 实现带重力效果的碰撞小球

前言

在屏幕可视区域内创建多个小球,每个小球的颜色、大小和初始速度都是随机的。 初始化的时候,小球会随机出现在屏幕中的任意位置,碰撞到水平线或者垂直线上(屏幕边缘),会对速度产生一定的影响,因为重力效果,小球最终会停留在水平线上,或者以一定的速度在水平线上左右运动。

效果预览

跳动的小球

实现步骤

画一个小球

1
<canvas></canvas>
1
2
3
4
5
6
7
8
// 获取元素
const canvas = document.querySelector('canvas');
// 设置画布的宽高 不能通过CSS设置
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 获取上下文 Canvas所有API(绘制图形)都是基于上下文
const ctx = canvas.getContext("2d");

定义创建球的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function Circle(x, y, radius, color) {
  // 坐标点x,y 半径 颜色
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.color = color;

  this.draw = function () {
    ctx.beginPath(); // 绘制路径 开始
    ctx.arc(this.x, this.y, this.radius, Math.PI * 2, false); // 画一个整圆,半圆是Math.PI
    ctx.fillStyle = this.color; // 设置要填充的颜色
    ctx.fill(); // 给图形填充颜色
    ctx.closePath(); //绘制路径结束
  };
}

调用方法实例化一个球

1
2
var ball = new Circle(100, 100, 30, "blue");
ball.draw();

这样,就完成了一个小球的绘制。

如果Canvas画布并不等于屏幕可视区域,屏幕边缘有滚动条,怎么解决?

1
2
3
4
5
/* 清除默认样式 */
body {
  margin: 0;
  overflow: hidden;
}

让球动起来

更新球的位置,限制球在屏幕内跳动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function Circle(x, y, dx, dy, radius, color) {
  // 坐标点x,y 半径 颜色
  // dx x轴方向的速度
  // dy y轴方向的速度
  this.x = x;
  this.y = y;
  this.dx = dx;
  this.dy = dy;
  this.radius = radius;
  this.color = color;

  this.draw = function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, Math.PI * 2, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();
  };

  this.update = function () {
    // 控制球在可视范围内/Canvas画布内
    if (this.y + this.radius + this.dy > canvas.height || this.y - this.radius <= 0) {
      this.dy = -this.dy;
    }
    // x方向 撞击到屏幕墙壁 就反向
    if (
      this.x + this.radius + this.dx > canvas.width ||
      this.x - this.radius <= 0
    ) {
      // this.x + this.radius + this.dx 是判断下一次move 是否会超过边界
      this.dx = -this.dx;
    }
    this.x += this.dx;
    this.y += this.dy;
    this.draw();
  };
}

使用requestAnimationFrame,让动画按照屏幕帧率执行

1
2
3
4
5
6
7
8
var ball = new Circle(100, 100, 3, 1, 30, "blue");
function animate() {
  requestAnimationFrame(animate);
  // 每屏绘制都要清除上一次的绘制 不然就会一直叠加 会把行动轨迹都绘制出来
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
  ball.update();
}
animate();

增加重力效果和摩擦系数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var friction = 0.9; // 摩擦系数 系数越大 这些球恢复平静的时间就越久
var gravity = 1; // 重力

function Circle(x, y, dx, dy, radius, color) {
  // 坐标点x,y 半径 颜色
  // dx x轴方向的速度
  // dy y轴方向的速度
  this.x = x;
  this.y = y;
  this.dx = dx;
  this.dy = dy;
  this.radius = radius;
  this.color = color;

  this.draw = function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, Math.PI * 2, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.stroke(); // 加上边框
    ctx.closePath();
  };

  this.update = function () {
    if (this.y - this.radius <= 0) {
      this.dy = -this.dy;
    }
    if (this.y + this.radius + this.dy > canvas.height) {
      // 每次撞击到地面,速度会降低(摩擦力)
      this.dy = -this.dy * friction;
    } else {
      // 在空中的时候 都有一个垂直方向的重力 对y轴速度产生影响
      this.dy += gravity;
    }
    // x方向 撞击到屏幕墙壁 就反向
    if (
      this.x + this.radius + this.dx > canvas.width ||
      this.x - this.radius <= 0
    ) {
      // this.x + this.radius + this.dx 是判断下一次move 是否会超过边界
      this.dx = -this.dx;
    }
    this.x += this.dx;
    this.y += this.dy;
    this.draw();
  };
}

创建多个球 分散在屏幕任意位置

获取任意范围内的整数

1
2
3
function randomIntFromRange(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

获取任意颜色

1
2
3
function getRandomColor(colors) {
  return colors[Math.floor(Math.random() * colors.length)];
}

给多个球分配任意大小、速度和屏幕内任意位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let ballArray = [];
const colors = ["#de324c", "#f4895f", "#f8e16f", "#95cf92", "#369acc", "#9656a2"]; // 一些好看的颜色合集

function init() {
  for (let i = 0; i < 100; i++) {
    let radius = randomIntFromRange(2, 20);
    let x = randomIntFromRange(radius, window.innerWidth - radius); // 不能溢出屏幕
    let y = randomIntFromRange(radius, window.innerHeight - radius);
    let dx = randomIntFromRange(-2, 2);
    let dy = randomIntFromRange(-2, 2);
    let color = getRandomColor(colors);
    ballArray.push(new Circle(x, y, dx, dy, radius, color));
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function animate() {
  requestAnimationFrame(animate);
  // 每屏绘制都要清除上一次的绘制 不然就会一直叠加 会把行动轨迹都绘制出来
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
  // ball.update();
  for (let i = 0; i < ballArray.length; i++) {
    ballArray[i].update();
  }
}
init();
animate();

点击屏幕任意位置,再次实现动效

1
2
3
window.addEventListener("click", function () {
  init();
});

屏幕自适应

1
2
3
4
5
window.addEventListener("resize", function () {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  init();
});

源码链接

最后附上Codepen 在线地址 ,喜欢的可以点个💛

Job searching can be exhausting, and I’m not sure if I’ll find my dream job. So, for now, let’s take a break and enjoy some fun coding

Built with Hugo
Theme Stack designed by Jimmy