闲来无事,想着随便捣鼓一点东西玩玩
说说思路:
一 需要一个粒子类
模拟每一个烟花粒子,粒子有横坐标,纵坐标,半径,速度,颜色等属性,以及绘制的方法。 颜色这里我加了个初始化的方法,加了透明度的处理。代码如下:
// 粒子类
import Explode from "./explode.js";
class Particle {
constructor(ctx, x, y, radius = 5) {
this.ctx = ctx;
this.radius = radius;
this.x = x;
this.y = y;
this.rgb = null;
this.angle = 0;
}
// 初始化颜色
initColor({ r, g, b }) {
this.rgb = { r, g, b };
this.color = "rgba(" + r + ", " + g + ", " + b + ", " + 1 + ")";
}
setColorAlpha(alpha) {
this.color =
"rgba(" +
this.rgb.r +
", " +
this.rgb.g +
", " +
this.rgb.b +
", " +
1 +
")";
}
paint() {
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
this.ctx.fillStyle = this.color;
this.ctx.fill();
this.ctx.closePath();
this.ctx.save();
}
del() {
delete this;
}
}
export default Particle;
2 需要一个发射的类
其实一次发射就是一个粒子从底部到天空的过程,这里我把发射类继承了粒子类。代码如下:
import Particle from "./particle.js";
import { getRandomColorRGB } from "./utils.js";
import Explode from "./explode.js";
// 随机生成一个发射角度 从-30度到30度
const getRandomAngle = () => {
return Math.random() * (Math.PI / 3) - Math.PI / 6;
};
// 生成一个随机speed
const getRandomSpeed = () => {
return Math.random() * 10 + 15;
};
export default class OneShot extends Particle {
constructor(ctx, x, y) {
super(ctx, x, y);
this.speed = getRandomSpeed();
this.initColor(getRandomColorRGB());
this.paint();
this.angle = getRandomAngle();
this.explode = null;
this.firstBoom = true;
}
launch() {
if (this.speed <= 0) {
// 如果速度小于等于0,就判断为到了爆炸阶段,进行爆炸的逻辑
// 第一次进入爆炸逻辑,需生成一个爆炸实例
if (this.explode) {
this.explode.boom();
} else {
this.explode = new Explode(this.ctx, this.x, this.y, this.rgb);
}
// this.del();
return;
} else {
// 发射阶段,发射角度是随机生成的(竖直方向往两边各偏移30度的范围内)
this.x += this.speed * Math.sin(this.angle);
this.y -= this.speed * Math.cos(this.angle);
this.speed -= 0.4;
this.paint();
}
}
}
3 需要一个爆炸类,模拟一次烟花发射到顶点时到爆炸效果。
每次到爆炸阶段,都可以往数组中添加num个粒子,起点一致往四周散开。还需要考虑到真实情况中的引力加速度。代码如下:
// 随机生成20-30个粒子,给不同的初始速度
import Particle from "./particle.js";
// 随机生成一个发射角度 从0度到360度
const getRandomAngle = () => {
return Math.random() * (Math.PI * 2);
};
// 生成一个随机半径
const getRandomRadius = () => {
return Math.random() * 3;
};
// 生成一个随机speed
const getRandomSpeed = () => {
return Math.random() + 1;
};
export default class Explode {
constructor(ctx, x, y, colorRGB, num = 30) {
this.ctx = ctx;
this.x = x;
this.y = y;
this.list = [];
this.colorRGB = colorRGB;
// 爆炸数量
this.num = num;
this.init();
}
init() {
for (let i = 0; i < this.num; i++) {
let p = new Particle(this.ctx, this.x, this.y, getRandomRadius());
p.angle = getRandomAngle();
p.initColor(this.colorRGB);
p.speed = getRandomSpeed();
this.list.push(p);
}
}
boom() {
// 爆炸阶段粒子移动逻辑,由爆炸中心往四周散开,角度随机从0-2*PI
if (this.list.length === 0) return;
for (let i = 0; i < this.list.length; i++) {
const cur = this.list[i];
cur.x += cur.speed * Math.sin(cur.angle);
cur.y = cur.y + 0.2 - cur.speed * Math.cos(cur.angle);
cur.paint();
cur.speed -= 0.01;
if (cur.speed < 0) {
this.list.splice(i, 1);
}
}
}
}
最后,再贴出index,html的代码:
<!--
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2024-02-01 15:13:28
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2024-05-28 21:32:51
* @FilePath: /practice/index.html
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
width: 100vw;
height: 100vh;
background-color: black;
}
.firework {
width: 2px;
height: 2px;
background-color: red;
position: absolute;
}
#myCanvas {
width: 100%;
height: 100%;
}
.bot {
width: 100px;
height: 40px;
background: lightcoral;
position: absolute;
bottom: 0;
left: 50%;
margin-left: -50px;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="100px"></canvas>
<div class="bot"></div>
</body>
<script src="./main.js" type="module"></script>
</html>
当然,主方法一定要贴出来:
import oneShot from "./oneShot.js";
// 获取 canvas 元素
const canvas = document.getElementById("myCanvas");
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
const ctx = canvas.getContext("2d");
window.W = canvas.width;
window.H = canvas.height;
const startX = canvas.width / 2;
const startY = canvas.height - 40 - 5;
const pList = [];
// 生成烟花粒子
for (let i = 0; i < 20; i++) {
let p = new oneShot(ctx, startX, startY);
pList.push(p);
}
// 主进程方法
let timer;
function main() {
// ctx.clearRect(0, 0, W, H);
pList.forEach((p) => p.launch());
// 这里每次都绘制一个蒙层,来模拟拖尾的效果
ctx.beginPath();
ctx.fillStyle = "rgba(0,0,0,0.1)";
ctx.fillRect(0, 0, W, H);
ctx.closePath();
ctx.save();
timer = requestAnimationFrame(main);
}
requestAnimationFrame(main);
ok,废话不多说,看看效果:
烟花-canvas
老表,学废了没?