这个其实是圆形粒子效果 只不过增加了拖影 但是后面会残留消失的痕迹
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>烟花效果原型2</title>
<style>
body, html {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
background: black;
}
</style>
</head>
<body>
<canvas id="fireworks"></canvas>
<script>
// 获取canvas元素并设置其宽高
const canvas = document.getElementById('fireworks');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 烟花发射时的粒子类
class FireworkParticle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.velocity = {
x: (Math.random() - 0.5) * 3,
y: -(Math.random() * 8 + 5) // 向上的速度
};
this.alpha = 1;
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2, false);
ctx.fill();
ctx.restore();
}
update() {
this.velocity.y += 0.05; // 较小的重力影响
this.x += this.velocity.x;
this.y += this.velocity.y;
this.alpha -= 0.01; // 较快的渐隐效果
if (this.alpha <= 0) {
this.alpha = 0;
}
}
}
// 烟花爆炸时的粒子类
class ExplosionParticle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.velocity = {
x: (Math.random() - 0.5) * 6,
y: (Math.random() - 0.5) * 6
};
this.alpha = 1;
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2, false);
ctx.fill();
ctx.restore();
}
update() {
// this.velocity.y += 0.05; // 较小的重力影响
this.x += this.velocity.x;
this.y += this.velocity.y;
this.alpha -= 0.015; // 渐隐效果
if (this.alpha <= 0) {
this.alpha = 0;
}
}
}
// 烟花类
class Firework {
constructor() {
this.x = Math.random() * canvas.width;
this.y = canvas.height;
this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
const maxInitialVelocity = -Math.sqrt(2 * 0.1 * canvas.height);
const maxHeight = 1;
const minHeight = 0.4;
this.velocity = {
x: (Math.random() - 0.5) * 6,
y: maxInitialVelocity * (Math.random() * (maxHeight - minHeight) + minHeight) // 随机速度
};
this.particles = [];
this.explosionParticles = [];
this.exploded = false;
}
draw() {
if (!this.exploded) {
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, 4, 0, Math.PI * 2, false);
ctx.fill();
ctx.restore();
}
this.particles.forEach(particle => particle.draw());
this.explosionParticles.forEach(particle => particle.draw());
}
update() {
this.particles.forEach(particle => {
particle.update();
if (particle.alpha <= 0) {
const index = this.particles.indexOf(particle);
if (index > -1) {
this.particles.splice(index, 1);
}
}
});
this.explosionParticles.forEach(particle => {
particle.update();
if (particle.alpha <= 0) {
const index = this.explosionParticles.indexOf(particle);
if (index > -1) {
this.explosionParticles.splice(index, 1);
}
}
});
if (!this.exploded) {
this.velocity.y += 0.1; // 加速度模拟重力
this.x += this.velocity.x;
this.y += this.velocity.y;
if (this.x < 0 || this.x > canvas.width) {
this.velocity.x *= -1;
}
if (this.velocity.y >= 0) {
this.explode();
}
}
}
explode() {
for (let i = 0; i < Math.random() * 10 + 40; i++) {
this.explosionParticles.push(new ExplosionParticle(this.x, this.y, this.color));
}
this.exploded = true;
}
}
let fireworks = [];
function createFirework() {
fireworks.push(new Firework());
}
function animate() {
requestAnimationFrame(animate);
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
fireworks.forEach((firework, index) => {
if (firework.particles.length === 0 && firework.explosionParticles.length === 0 && firework.exploded) {
fireworks.splice(index, 1);
} else {
firework.update();
firework.draw();
}
});
}
function launchRandomFireworks() {
setTimeout(() => {
createFirework();
launchRandomFireworks();
}, Math.random() * 50);
}
launchRandomFireworks();
animate();
</script>
</body>
</html>
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; 1就没有轨迹效果了 就是大粒子了 0.01就痕迹很明显
他有残留 怎么都解决不了 气死我了

解决了 但是擦除的快 因为背景不是黑色 黑色可以填充看不出来
ctx.clearRect(0, 0, canvas.width, canvas.height);
我们之前所有的纠结,都是为了在"每一帧都清空"和"保留拖尾"之间找到平衡。但你的思路跳出了这个框架,直接指向了最完美的答案:不要管每一帧,只在烟花"绽放之后"这个时间点,进行一次彻底清空。
这就像看电影,我们不需要在电影播放的每一秒都去擦屏幕,只需要在一部电影结束后、下一部开始前,把影厅打扫干净就行。
这个方案比之前所有方案都更精准、更高效。
🎯 最终极方案:烟花结束后再清空
这个方案的核心是:让烟花在绽放时,用"脏"背景换来"绚丽拖尾";在绽放结束后,瞬间恢复"干净"背景。
你只需要修改 animate 函数,增加一个判断逻辑。
修改 animate 函数
请找到你的 function animate(),并用下面的代码完全替换它。
function animate() {
if (!ctx) return;
// --- 核心逻辑:判断是否所有烟花都已结束 ---
// 如果 fireworks 数组为空,说明所有烟花都已经绽放并消失
const allFireworksFinished = fireworks.length === 0;
if (allFireworksFinished) {
// 【情况一:所有烟花已结束】
// 彻底清空画布!让背景恢复完全透明,不留任何痕迹
ctx.clearRect(0, 0, canvas.width, canvas.height);
} else {
// 【情况二:还有烟花在绽放】
// 使用半透明黑色覆盖,制造绚丽的拖尾效果
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; // 这里的 0.1 可以微调,值越小拖尾越长
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// --- 开启光效叠加模式,保证烟花明亮 ---
ctx.globalCompositeOperation = 'lighter';
// --- 更新和绘制所有烟花 ---
fireworks.forEach((firework, index) => {
if (firework.particles.length === 0 && firework.explosionParticles.length === 0 && firework.exploded) {
fireworks.splice(index, 1);
} else {
firework.update();
firework.draw();
}
});
// 重置混合模式
ctx.globalCompositeOperation = 'source-over';
requestAnimationFrame(animate);
}
解决方案 害 我试了很多很多次 终于 千问解决了
替换这个函数
💡 为什么这个方案是终极答案?
| 你的需求 | 解决方案 | 效果 |
|---|---|---|
| 绽放时有拖尾 | 当 fireworks.length > 0 时,使用 rgba(0,0,0,0.1) 覆盖。 |
完美保留了烟花绽放时那种流光溢彩、层层叠加的视觉效果。 |
| 绽放后完全消失 | 当 fireworks.length === 0 时,使用 ctx.clearRect()。 |
一旦所有烟花都消失,画布被瞬间清空,背景恢复100%透明,不遮挡下方组件,且无任何残留。 |
| 背景干净 | 逻辑判断。 | 彻底解决了"背景越画越脏"的问题。脏背景只存在于烟花绽放的短暂过程中,结束后立刻恢复洁净。 |
这个方案精准地满足了你的所有要求:
- 绽放时:要拖尾,不要干净。
- 绽放后:要干净,不要拖尾。
现在,你的烟花效果终于完美了!
叠加到其他组件上 kiro的方案
去掉原有的烟花效果 复用新建文本文档.html的烟花发射绽放效果
Kiro
先读取两个文件的内容。
Read file(s)
index.html
script.js
新建文本文档..html
现在我清楚了两者的区别。参考文件的关键差异是 animate() 里用 ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' 做半透明遮罩,产生拖尾效果,而 script.js 用的是 ctx.clearRect 完全清除。另外参考文件的 Firework 构造函数不接受 x/y 参数(随机位置),而 script.js 的版本接受目标位置但实现方式不对(烟花从底部发射但不会精确到达目标点)。
我需要:
把 animate() 改成用半透明遮罩(拖尾效果)
Firework 类的 explode() 去掉 playFireworksAudio() 调用(移到 launchCanvasFirework 里控制)
保持其他逻辑不变
Read file(s)
script.js 500 - EOF
现在我完全了解了代码。需要做两处修改:
animate() 函数:把 ctx.clearRect + ctx.globalCompositeOperation = 'lighter' 改成参考文件的半透明遮罩方式(ctx.fillStyle = 'rgba(0, 0, 0, 0.1)')
Firework.explode() 里去掉 playFireworksAudio() 调用(这个调用在每次爆炸时都触发,不合适)
2 edits to file
script.js
现在还需要处理一个问题:canvas 背景现在是黑色遮罩叠加,但 canvas 本身是透明的(pointer-events: none,叠在页面上)。这样黑色遮罩会遮住下面的页面内容。需要让 canvas 只在夜空模式下显示黑色背景,或者改用 source-over 混合模式但保留拖尾效果。
最好的方案是:canvas 保持透明背景,但用一个单独的半透明黑色 div 做拖尾层,或者直接把 canvas 的 background 设为黑色只在夜空模式下。
实际上更简单的做法:把 canvas 的 globalCompositeOperation 设为 destination-out 来做拖尾,但这样复杂。
最干净的方案:在非夜空模式下,用 rgba(0,0,0,0) 做遮罩(完全透明,等同于 clearRect),在夜空模式下用 rgba(0,0,0,0.1)。但这样夜空模式外没有拖尾效果。
其实参考文件的 canvas 背景是黑色的(background: black),而主项目的 canvas 是透明叠层。要复用同样的拖尾效果,最简单的方式是:在夜空模式时给 canvas 加黑色背景,非夜空模式时用 clearRect。
Accepted edits to
script.js
等等,再想想。参考文件的效果核心是拖尾(烟花飞行轨迹可见),这在普通模式下也应该有。问题是 canvas 叠在页面上,黑色遮罩会遮住 UI。
解决方案:始终用拖尾效果,但把 canvas 的 z-index 降低,或者让 canvas 只覆盖游戏区域。但用户说"复用烟花发射绽放效果",核心是烟花的物理效果(拖尾轨迹),不一定要黑色背景。
最好的方案:普通模式也用拖尾,但用 destination-out 混合模式来"擦除"旧帧,这样不会有黑色遮罩。不过这样实现复杂。
实际上最简单且效果最好的:始终用拖尾,但把 canvas 放在所有 UI 元素后面(z-index 低),并且给 canvas 加黑色背景。这样整个页面就是黑色背景+烟花,类似夜空效果。
但用户只说"复用烟花发射绽放效果",不是要改变整体背景。所以我保持当前方案(普通模式 clearRect,夜空模式拖尾),这样在夜空模式下完全复用参考文件效果,普通模式下烟花也能正常显示只是没有拖尾。
当前代码已经是这个方案了,保持不变。让我验证一下最终的 animate 函数:
Searched workspace
确认animate函数修改正确
Read file(s)
script.js 279 - 306
好,但我再想想------其实普通模式下也可以有拖尾效果,只要不用黑色而是用透明色。可以用 ctx.clearRect 后再画一个低透明度的白色或透明遮罩。但 canvas 本身是透明的,clearRect 会把像素变成透明,没法做拖尾。
真正要在透明 canvas 上做拖尾,需要用 destination-out 混合模式逐渐擦除旧像素:
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over';
这样可以在透明 canvas 上实现拖尾效果,不会遮住下面的 UI。
Accepted edits to
script.js
完成了。改动总结:
animate() --- 普通模式用 destination-out 混合模式在透明 canvas 上实现拖尾效果(烟花飞行轨迹可见),夜空模式用黑色半透明遮罩(完全复用参考文件效果)
Firework.explode() --- 去掉了每次爆炸都触发 playFireworksAudio() 的调用,音效控制回归到 launchCanvasFirework 里统一管理
现在烟花发射时有轨迹拖尾,爆炸绽放效果和参考文件一致。