【HTML】过年不能放烟花,那就放电子烟花


🌈个人主页:鑫宝Code
🔥热门专栏:闲话杂谈| 炫酷HTML | JavaScript基础 **
💫个人格言: "如无必要,勿增实体"**

文章目录
-
-
闲谈
-
开干
-
- 初始化粒子
- 粒子聚合成烟花
- 让粒子动起来
-
全部代码
-
闲谈
普通百姓过节嘛……总要放点什么吧。不过也不全是哦……实在没有办法嘛……毕竟城市里人太多……反正咱们懂行嘛……就想着借助现代科技来过个好年……既省心又省力。


开干
第一步,请明确整个思路;我认为可将其分解为两个关键步骤:第一部分涉及Canvas元素的动态更新;第二部分则需结合requestAnimationFrame机制完成动画渲染;此外还需运用基础的物理运动学原理来计算物体轨迹参数;最后别忘了加入一些辅助说明文字以提高可读性
首先随后生成一个烟花爆炸产生的粒子,这需要综合运用Canvas技术和基础物理知识。
最终利用动画技术将各个粒子的运动轨迹依次连接起来。
大家看下,我感觉应该没什么问题了,于是深入细节分析。
初始化粒子
我们先分析一下这个粒子有哪些属性,我罗列如下
- 粒子的初始位置坐标(x,y)
- 粒子的速度分量(Vx,Vy)
- 粒子的颜色为Color
- 粒子的大小参数Radius
- 粒子的透明度随粒子下落而逐渐减弱(opacity)
属性分析已经完成之后,我们将深入探讨一个关键问题:在爆炸瞬间,粒子是如何运动的?由此可见,这些粒子均具有初始速度,其运动轨迹遵循自由落体模式。

我们将速度分成水平方向Vx以及竖直方向Vy,于是我们可以得到
Vx在整个运动过程中保持恒定。
Vy的速度遵循公式:Vy = Vy - gt;这意味着每秒钟会减少g个单位的速度;由于g是一个固定值,在模拟过程中我们采用每隔10ms(即屏幕刷新周期)减少0.15px的速度变化。
该变量Vy在代码中表示纵向运动的速度,在实现物理动画时需要特别注意其变化方向与屏幕坐标系的关系。具体来说,在计算机图形界面中,默认的屏幕坐标系以右上角为原点(即x=0,y=0的位置),因此向下运动的方向与数学坐标系中的正y轴方向一致。因此,在本例中使用加号来表示纵向速度的变化量(如Vy += 0.15)是为了确保向下运动的速度为正数(即往上走速度为负数),从而符合直观的方向性定义。
于是我们便可撰写如下代码
class Dot {
constructor(x, y, color, Vx, Vy) {
this.x = x;
this.y = y;
this.Vx = Vx;
this.Vy = Vy;
this.color = color;
this.radius = 2.5;
this.opacity = 1;
}
update() {
// 每一帧x和y轴移动的距离
this.x += this.Vx;
this.y += this.Vy;
// 每一帧速度变化
this.Vy += 0.15;
// 每一帧清晰度较小
this.opacity = this.opacity - 0.01;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.globalAlpha = this.opacity;
ctx.fill();
}
}
粒子聚合成烟花
怎样才能将粒子聚合成烟花?我们可以创建一个烟花类就足够了。对于任何一个烟花而言,在确定爆炸的位置上是关键的——这主要涉及x轴和y轴的坐标值。
- 粒子的色调如何? 为了提升粒子色调的饱和度以获得更丰富的视觉效果,在视觉效果设计中我们会采用基于HSL色彩模型的方案。值得注意的是,在色彩表示方法中与RGB与HEX编码体系不同的是HSL模型通过色相、饱和度与明度三个参数来精确控制色调。具体实现时使用的代码逻辑如下:
// 是JavaScript中动态生成一个随机色相、饱和度为100%、亮度为50%的HSL颜色值。
const color = `hsl(${Math.random() * 360}, 100%, 50%)`;
对于粒子的初始速度设定?我们可以设定Vx速度的范围在[-x,x]区间内,并支持粒子在横向轴上向左或向右运动。而对于Vy速度,则全部采用向上的方向,并且由于屏幕坐标系中右上角为(0,0)点的原因,默认向上运动的速度被定义为负值。
const Vx = (Math.random() - 0.5) * 6;
const Vy = -Math.random() * 10;
于是我们可以将烟花类写好
class Firework {
constructor(x, y) {
this.x = x;
this.y = y;
this.dots = [];
// 一次性放40个烟花粒子
for (let i = 0; i < 40; i++) {
const color = `hsl(${Math.random() * 360}, 100%, 50%)`;
const Vx = (Math.random() - 0.5) * 6;
const Vy = -Math.random() * 10;
this.dots.push(new Dot(x, y, color, Vx, Vy));
}
}
draw() {
this.dots.forEach((particle) => particle.update());
this.dots.forEach((particle) => particle.draw());
}
}
让粒子动起来
我们拥有一批非常有趣的粒子了。如何让这些粒子开始移动呢?当然调用请求AnimationFrame方法,并参考文档中的相关内容。
为了实现动画效果并确保在页面刷新前更新画面,请使用该方法并传递所需的回调函数。
直接调用Firework的draw于是我们就能得到这样的效果。

但是粒子已经有了,但我们要的是粒子像流星那样有一个尾巴.该怎么设置呢?
ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
之后,我们可以通过点击屏幕,触发这个烟花绽放。相应的代码如下
// 鼠标点击触发烟花效果
canvas.addEventListener("click", (event) => {
const x = event.clientX;
const y = event.clientY;
// 所有的烟花
fireworks.push(new Firework(x, y));
});
function animate() {
ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
fireworks.forEach((firework, index) => {
// 去除透明度为0的烟花
if (firework.dots[0].opacity <= 0) {
fireworks.splice(index, 1);
} else {
firework.draw();
}
});
requestAnimationFrame(animate);
}
对应的效果图如下:

全部代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>烟花效果</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<canvas id="fireworksCanvas"></canvas>
<script>
const canvas = document.getElementById("fireworksCanvas");
const ctx = canvas.getContext("2d");
// 所有烟花的集合
let fireworks = [];
// 设置画布大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 监听窗口大小变化
window.addEventListener("resize", () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
class Dot {
constructor(x, y, color, Vx, Vy) {
this.x = x;
this.y = y;
this.Vx = Vx;
this.Vy = Vy;
this.color = color;
this.radius = 2.5;
this.opacity = 1;
}
update() {
this.x += this.Vx;
this.y += this.Vy;
this.Vy += 0.15;
this.opacity = this.opacity - 0.01;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.globalAlpha = this.opacity;
ctx.fill();
}
}
class Firework {
constructor(x, y) {
this.x = x;
this.y = y;
this.dots = [];
for (let i = 0; i < 40; i++) {
const color = `hsl(${Math.random() * 360}, 100%, 50%)`;
const Vx = (Math.random() - 0.5) * 6;
const Vy = -Math.random() * 10;
this.dots.push(new Dot(x, y, color, Vx, Vy));
}
}
draw() {
this.dots.forEach((particle) => particle.update());
this.dots.forEach((particle) => particle.draw());
}
}
// 鼠标点击触发烟花效果
canvas.addEventListener("click", (event) => {
const x = event.clientX;
const y = event.clientY;
fireworks.push(new Firework(x, y));
});
function animate() {
ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
fireworks.forEach((firework, index) => {
if (firework.dots[0].opacity <= 0) {
fireworks.splice(index, 1);
} else {
firework.draw();
}
});
requestAnimationFrame(animate);
}
// 启动动画
animate();
</script>
</body>
</html>
