Canvas放个烟花
文章目录
- 一、效果图
- 二、制作
- 首先在HTML页面上生成canvas元素
* 2. 进行基本样式配置
* 3. 编写代码实现动态效果
* * (1) 基本配置
-
(2) 创建烟花弹类
-
(3) 创建粒子类
-
(4) 实现效果函数
-
(5) 初始化效果准备
-
三、源码
-
四、参考资料
一、效果图


由于GIF图帧率的限制,画面不是非常流畅,运行画面是比较流畅的
二、制作
1、首先在html页面上创建canvas元素
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./firework.css">
<title>🎇烟花</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="./firework.js"></script>
</body>
</html>
导入基础CSS样式表以配置外观,并随后导入<script src="./firework.js"></script>这一段JavaScript代码。烟花效果的核心代码位于该文件中。确保正确加载顺序:首先应在元素完全渲染后触发JavaScript脚本执行;另外,在HTML结构完成时为JavaScript文件添加defer属性可实现延迟加载。
2、基础样式设置
创建firework.css文件,进行简单的样式设置:
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background-color: black;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}
canvas {
position: relative;
}
3、编写
(1)基础设置
获取页面的canvas元素,并设置画布的宽高,获取其上下文。
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 获取canvas的上下文
const ctx = canvas.getContext('2d');
烟花主要包含两种类型:烟花弹与爆炸粒子。因此需要为每种烟花类型创建一个独立的数据存储结构,在后续处理过程中对这两种类型进行统一化操作。
/** 烟花弹数组 */
const fireworksArray = [];
/** 粒子数组 */
const particlesArry = [];
(2)定义烟花弹类
烟花弹类包含三个核心方法:constructor()构造函数、update()更新函数以及render()绘制函数。整个流程中应首先执行更新步骤,在此基础上进行渲染(即绘制图像)。通过不断重复上述步骤可实现动态效果。
/** * 烟花弹类
* @param {number} thick 烟花的大小
*/
class Firework {
constructor(thick = 2) {
this.y = canvas.height;
//水平位置随机
this.x = Math.random() * canvas.width | 0;
// 重力参数
this.vel = -(Math.random() * Math.sqrt(canvas.height) / 3 + Math.sqrt(4 * canvas.height) / 2) / 5;
// 颜色随机
this.c = "hsl(" + (Math.random() * 360 | 0) +",100%,60%)";
// 烟花大小
this.thick = thick;
}
update() {
/** y轴坐标减少,达到上升的效果,坐标原点默认再左上角 */
this.y += this.vel;
this.vel += 0.04;
// 当vel = 0时,烟花到达最高点,产生爆炸粒子,并高度重置
if(this.vel >= 0) {
// 每个爆炸产生的粒子数范围(可剥离)
const count = getRandomInt(50,250);
for (let i = 0; i < count; i++) {
particlesArry.push(new Particle(this.x,this.y,this.c))
}
// 高度重置
this.y = canvas.height;
this.x = Math.random() * canvas.width | 0;
this.vel = -(Math.random() * Math.sqrt(canvas.height) / 3 + Math.sqrt(4 * canvas.height) / 2) / 5;
this.c = "hsl(" + (Math.random() * 360 | 0) + ",100%,60%)";
}
}
/** * 绘制烟花弹
*/
draw() {
// 它影响所有后续绘制操作的透明度,直到改变该属性的值。
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.arc(this.x, this.y, this.thick, 0, Math.PI*2);
ctx.fill()
}
}
constructor()方法设定烟花弹起始点的位置,在水平方向上的任意位置,并位于垂直方向与屏幕底端对齐的位置。(由于canvas坐标系默认左上方为原点,在此系统中向上增长)
vel参数解释:
vel 被视为重力参数,在此模型中用于描述烟花弹上升阶段的速度变化情况。其初始值通过以下公式计算得出:- \left( \frac{\text{Math.random()} \times \sqrt{\text{canvas.height}}}{3} + \frac{\sqrt{4 \times \text{canvas.height}}}{2} \right ) / 5。这一数值通常为负值,并随着烟花弹的高度增加而增大。在每次更新步骤中,“this.y”增加当前速度值,“this.vel”则增加固定量的增量(即+0.04),从而使得速度变化幅度逐渐减小。这一机制旨在模拟受重力作用的影响效果:当烟花弹的高度越高时(即距离地面越远),其受到的加速度就越小。
在每次调用 update() 函数时, 系统会执行以下操作: 首先, 更新烟花弹当前位置; 接着, 重绘 draw() 方法以呈现最新状态. 当速度参数 vel 降到小于零时, 即表示烟花已达到顶点, 此时系统会生成一个新的粒子对象 new Particle, 模拟爆炸效果. 并立即重置烟花的位置, 并从屏幕底部重新生成.
(3)定义粒子类
粒子与烟花弹本质上相同, 但它们的行为存在差异. 例如, 粒子的起始坐标应当等于烟花弹末端的位置坐标, 而且位置的变化并非仅限于y轴方向, 而是同时涉及x轴与y轴的变化.
其中vx和vy分别代表粒子在x轴和y轴上的速度变化量,并且它们各自都有50%的可能性取正值或负值。这种组合方式能够使粒子向不同的方向运动而不局限在一个单一的方向上。
// 粒子变化的方向,上下左右均为50%的概率
this.vx = (0.5 - Math.random()) * 100;
this.vy = (0.5 - Math.random()) * 100;
// 生命值(变化次数)
this.age = Math.random() * 100 | 0;
当坐标的值若加上一个正的变化量会增加时,则相反地,则会减少。需要注意的是,在该方向上的变化并不是固定不变的;为了模拟重力的效果,在该方向上应具有逐渐增大的速度增量。具体而言,在沿负Y方向(即Y坐标向下)时对应数值会增大;因此,在代码实现中我们可以通过执行\texttt{this.vy}++来实现沿该方向的速度增量逐步增加。
// x轴上变化恒定
this.x += this.vx /20;
this.y += this.vy /20;
// 在y轴上一直增加变大,y轴是向下变大的,这样模拟粒子下落的效果
this.vy++;
this.age--;
单个粒子类代码如下:
/** * 粒子类
* @param {number} x 爆炸坐标x
* @param {number} y 爆炸坐标y
* @param {string} color 爆炸粒子颜色
* @param {number} thick 爆炸粒子大小
*/
class Particle {
constructor(x,y,color,thick = 0.5) {
this.x = x;
this.y = y;
this.c = color;
// 粒子变化的方向,上下左右均为50%的概率
this.vx = (0.5 - Math.random()) * 100;
this.vy = (0.5 - Math.random()) * 100;
//生命值
this.age = Math.random() * 100 | 0;
// 粒子的大小
this.thick = thick;
}
update() {
// x轴上变化恒定
this.x += this.vx /20;
this.y += this.vy /20;
// 在y轴上一直增加变大,y轴是向下变大的,这样模拟粒子下落的效果
this.vy++;
this.age--;
}
draw() {
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.arc(this.x, this.y, 0.5, 0, Math.PI*2);
ctx.fill();
}
}
当创建粒子时,在constructor()方法中需传递起始位置与颜色信息。对于烟花爆炸效果中的粒子来说,在发射后达到最高点时设置起始位置更为合适。因此,在烟花弹上升至顶峰的过程中,并将其当前位置作为起始点以便于后续效果渲染。此外还可以指定粒子大小参数
由于此类只能生成单一方向变化的一个粒子,在无法实现向四周扩散的情况下,则需通过循环生成多个这样的颗粒以模拟爆炸效果。考虑到每次爆炸可能会导致不同的扩散情况,在实际应用中应通过随机算法设定一定的数量范围来实现预期效果
const count = getRandomInt(50,250);
for (let i = 0; i < count ; i++) {
particlesArry.push(new Particle(this.x,this.y,this.c));
}
(4)绘制函数
该函数的实现主要用于遍历烟花弹数组fireworksArray[]以及可能存在的粒子数组 particlesArry[](当有对应数据时),逐个获取其中的实例对象,并依次调用其update()与draw()方法进行绘图操作。这种机制确保了在每一帧画面中都能完整呈现所有当前存在的烟花弹和粒子元素,并通过递归机制不断更新与绘制过程以达到平滑过渡的画面效果
function draw() {
ctx.globalAlpha = 0.1;
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < fireworksArray.length; i++) {
fireworksArray[i].update();
fireworksArray[i].draw();
}
for (let i = 0; i < particlesArry.length; i++) {
particlesArry[i].update();
particlesArry[i].draw();
if(particlesArry[i].age <0 ) {
particlesArry.splice(i,1);
}
}
// 递归,每一帧调用一次,且该函数为回调函数,避免栈溢出
requestAnimationFrame(draw);
}
(5)初始化函数
初始化函数被用来生成烟花弹实例,并将该实例存储到fireworksArray数组中。
/** * 一个初始化函数
*/
function init_fireworks() {
// 宽度上平均100个
for (let i = 0; i < (canvas.width / 100 | 0); i++) {
fireworksArray.push(new Firework);
}
}
函数调用:
init_fireworks();
draw();
三、源码
HTML:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./firework.css">
<title>🎇烟花</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="./firework.js"></script>
</body>
</html>
CSS
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background-color: black;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}
canvas {
position: relative;
}
JavaScript
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 获取canvas的上下文
const ctx = canvas.getContext('2d');
/** 烟花弹数组 */
const fireworksArray = [];
/** 粒子数组 */
const particlesArry = [];
/** * 产生 min - max 的随机整数
* @param {number} min
* @param {number} max
* @return
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/** * 烟花类
* @param {number} thick 烟花的大小
*/
class Firework {
constructor(thick = 2) {
this.y = canvas.height;
//水平位置随机
this.x = Math.random() * canvas.width | 0;
// 重力参数
this.vel = -(Math.random() * Math.sqrt(canvas.height) / 3 + Math.sqrt(4 * canvas.height) / 2) / 5;
// 颜色随机
this.c = "hsl(" + (Math.random() * 360 | 0) +",100%,60%)";
// 烟花大小
this.thick = thick;
}
update() {
/** y轴坐标减少,达到上升的效果,坐标原点默认再左上角 */
this.y += this.vel;
this.vel += 0.04;
// 当vel = 0时,烟花到达最高点,产生爆炸粒子,并高度重置
if(this.vel >= 0) {
// 每个爆炸产生的粒子数范围(可剥离)
const count = getRandomInt(50,250);
for (let i = 0; i < count; i++) {
particlesArry.push(new Particle(this.x,this.y,this.c))
}
// 高度重置
this.y = canvas.height;
this.x = Math.random() * canvas.width | 0;
this.vel = -(Math.random() * Math.sqrt(canvas.height) / 3 + Math.sqrt(4 * canvas.height) / 2) / 5;
this.c = "hsl(" + (Math.random() * 360 | 0) + ",100%,60%)";
}
}
/** * 绘制烟花弹
*/
draw() {
// 它影响所有后续绘制操作的透明度,直到改变该属性的值。
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.arc(this.x, this.y, this.thick, 0, Math.PI*2);
ctx.fill()
}
}
/** * 粒子类
* @param {number} x 爆炸坐标x
* @param {number} y 爆炸坐标y
* @param {string} color 爆炸粒子颜色
* @param {number} thick 爆炸粒子大小
*/
class Particle {
constructor(x,y,color,thick = 0.5) {
this.x = x;
this.y = y;
this.c = color;
// 粒子变化的方向,上下左右均为50%的概率
this.vx = (0.5 - Math.random()) * 100;
this.vy = (0.5 - Math.random()) * 100;
// 生命值
this.age = Math.random() * 100 | 0;
// 粒子的大小
this.thick = thick;
}
update() {
// x轴上变化恒定
this.x += this.vx /20;
this.y += this.vy /20;
// 在y轴上一直增加变大,y轴是向下变大的,这样模拟粒子下落的效果
this.vy++;
this.age--;
}
draw() {
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.arc(this.x, this.y, 0.5, 0, Math.PI*2);
ctx.fill();
}
}
function draw() {
ctx.globalAlpha = 0.1;
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < fireworksArray.length; i++) {
fireworksArray[i].update();
fireworksArray[i].draw();
}
for (let i = 0; i < particlesArry.length; i++) {
particlesArry[i].update();
particlesArry[i].draw();
if(particlesArry[i].age <0 ) {
particlesArry.splice(i,1);
}
}
// 递归,每一帧调用一次,且该函数为回调函数,避免栈溢出
requestAnimationFrame(draw);
}
/** * 一个初始化函数
*/
function init_fireworks() {
// 宽度上平均100个
for (let i = 0; i < (canvas.width / 100 | 0); i++) {
fireworksArray.push(new Firework);
}
}
init_fireworks();
draw();
四、参考资料
此乃教学视频:借助Canvas技术呈现炸开式的礼花效果!完整代码如下:$functionality = function (canvas) { ... }
