皇甫佳文 发表于 2025-6-3 00:32:09

制作一个炫酷的多小球碰碰的 JS 网页特效,入门弹性碰撞模拟和类的应用

目录

[*]前言
[*]先画一个圆
[*]完善我们的类
[*]小球动起来
[*]最简单的碰撞计算,接触墙壁反弹
[*]向量类的完善
[*]检测两小球之间的碰撞
[*]完善碰撞的效果
[*]重复计算的问题
[*]撞击墙壁定格问题
[*]内存问题
[*]随机数生成多个小球
[*]参考资料

前言

在前端开发里,canvas 是 HTML5 里最炫酷的工具。我们今天就来搞一个这样的梦幻的效果,学习一下 ES6 的类在开发一个完整项目的思路(即 ES5 的构造函数),还有物理碰撞的程序的实现,当然,效果也很酷炫!
完整代码在此处。
先画一个圆

使用“类”这种被广泛应用的面向对象的概念,我们可以更好的整理我们的代码,做出更大的项目。
所以我们先创建一个画板的类 class Canvas { } ,以便抽象我们之后对的操作。
然后再向类里添加第一个方法 drawCircle() ,作为我们的测试吧,就是先画一个最简单的元素 --- 圆!
完整代码如下 (可以在 这个编辑器 进行简单调试):
在代码里,我们定义了一个圆的属性,即 位置 x y 和半径 、 颜色。通过这种井井有条又优雅的方式,我们的目的就达到了!

这就是一切的基础,一切从这里开始。
完善我们的类

我们直接使用 ball 显然是不够的,小球它们要有自己的思想,我们的 Canvas 类要只负责绘制,所以我们需要重新开辟一个类,叫 Ball 类,来处理它们自己的“思想”。
而 canvas 类也需要更多的可扩展性,今天我们是画圆,明天我们想画圈、方块,我们也要考虑到,所以现在,我们要完善一下。
完整代码如下,这样就完美了 ~
图像能画出来,那么下一步就是运动了。这个要复杂了,一下子想不到要怎么弄,所以要一步一步来。
小球动起来

我们想一下,小球动起来,必定需要把画板清空,然后更改位置、绘制,再清空,再更改位置、绘制... 一帧一帧来。
所以,

[*]画板需要有一个方法,清空画板 方法
[*]计算小球下一帧的位置
[*]再封装一个 【一键更新数据】,用于操作更新数据的逻辑,以及记录和返回计算的结果(表示当前一帧整个游戏的宏观状态)
(第三点的这种思想,可以看这个文章)
先实现第一条,这个很好搞,canvas 只需要使用白色画笔,画一个覆盖全画板的矩形即可:
(不过,我们可以不使用纯白,使用 0.4的透明度,可以一点一点将上一帧给缓缓刷白,效果很好!)
clearDisplay(){// 清空画布
        this.ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';// 这个透明度 0.4 是精华,绘制轨迹效果的关键
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}然后是第二条。
小球如果要运动,必然需要知道要往哪里运动。现在我们引入物理的概念 --- 速度(velocity),这是一个向量值。
而下一帧要去的地方,就是当前的位置,加上当前的速度向量。比如速度是向右 5m/s,那下一秒的位置就是当前位置加上向右 5 米。
这是属于球的个人的“思想”,所以我们写到 Ball 类里面,同时 球 也要加上 速度 这个属性,位置和速度都是向量,都是 x y。
(当然,向量又是一个复杂的个体,所以我们需要再单独开辟一个向量类 Vector )
// 球类
class Ball {
        constructor(config){
                Object.assign(this,{
                        type : 'circle',
                        position : new Vector(100, 100),// 位置也是向量
                        velocity : new Vector(5, 3),// 当前的速度
                        color : 'blue',
                        radius : 25,

                },config);
        }

        nextFrameUpdate(){// 计算下一帧,小球的位置
                return new Ball({
                        ...this,// 其他属性保持不变
                        position: this.position.add(this.velocity),// 所谓的计算,其实就是根据向量 +1
                });
        }
}在 canvas 里,x 和y的两个正方向如图所示,所以当前小球的速度是向右下:

下面就是我们当前的向量类 Vector :
// 向量(可作为位置 和 速度)
class Vector {
        constructor(x, y) {
                this.x = x;
                this.y = y;
        }

        add(vector) {// 两个向量相加,就是这样
                return new Vector(this.x + vector.x, this.y + vector.y);
        }
}然后,就是使用 js 里用烂了的 requestAnimationFrame 让这个画面一帧一帧动起来,它是根据浏览器的性能实时智能控制帧率的,一般是 100帧/s左右。不熟悉的同学可以看这个 MDN 的介绍 。

完整的代码如下:
最简单的碰撞计算,接触墙壁反弹

这个,还几乎用不到物理碰撞算法之类。其实实现这个功能特别简单,只需要检测到小球到达墙壁边界,然后相应的速度正负转化一下即可!
代码很简单,很易懂,将 Ball 类里的 nextFrameUpdate 计算下一帧位置 的这个方法添加两个判断即可:
nextFrameUpdate(displayState){// 计算下一帧,小球的位置

        // 如果小球左右到达边界,X 速度取反
        if (this.position.x >= displayState.displayEle.canvas.width - this.radius || this.position.x <= this.radius) {
                this.velocity = new Vector(-this.velocity.x, this.velocity.y);
        }

        // 如果小球上下到达边界,Y 速度取反
        if (this.position.y >= displayState.displayEle.canvas.height - this.radius || this.position.y <= this.radius) {
                this.velocity = new Vector(this.velocity.x, -this.velocity.y);
        }

        return new Ball({
                ...this,// 其他属性保持不变
                position: this.position.add(this.velocity),
        });
}每当 collisions 元素的数量达到 10 个以上,就只保留最后三个元素。
这样,我们就基本完成碰撞的检测和碰撞的效果了 ~ 我们来实验一下效果吧!
完整代码:
随机数生成多个小球

现在,我们就可以写一个循环和随机数结合的脚本,生成一大堆个小球,像开头的那个动画一样的效果了。
dotProduct(vector) {// 数量积
        return this.x * vector.x + this.y * vector.y;
}最后的效果如下面这个页内框架所示:
参考资料


[*]https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial
[*]https://gist.github.com/joshuabradley012/bd2bc96bbe1909ca8555a792d6a36e04
[*]https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional
[*]https://eloquentjavascript.net/16_game.html

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 制作一个炫酷的多小球碰碰的 JS 网页特效,入门弹性碰撞模拟和类的应用