(() => {
// 防止重复注入
if (window.__fireworksCleanup__) {
window.__fireworksCleanup__();
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.style.cssText = `
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 2147483647;
`;
document.documentElement.appendChild(canvas);
let w = canvas.width = window.innerWidth * devicePixelRatio;
let h = canvas.height = window.innerHeight * devicePixelRatio;
ctx.scale(devicePixelRatio, devicePixelRatio);
const fireworks = [];
const particles = [];
let running = true;
let rafId = null;
let autoLaunchTimer = null;
function rand(min, max) {
return Math.random() * (max - min) + min;
}
function hsl(h, s, l, a = 1) {
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
}
class Firework {
constructor(sx, sy, tx, ty) {
this.x = sx;
this.y = sy;
this.sx = sx;
this.sy = sy;
this.tx = tx;
this.ty = ty;
this.distanceToTarget = Math.hypot(tx - sx, ty - sy);
this.distanceTraveled = 0;
this.angle = Math.atan2(ty - sy, tx - sx);
this.speed = rand(4, 7);
this.acceleration = 1.03;
this.brightness = rand(50, 70);
this.hue = rand(0, 360);
this.trail = [];
this.trailLength = 5;
}
update(i) {
this.trail.push([this.x, this.y]);
if (this.trail.length > this.trailLength) this.trail.shift();
this.speed *= this.acceleration;
const vx = Math.cos(this.angle) * this.speed;
const vy = Math.sin(this.angle) * this.speed;
this.distanceTraveled = Math.hypot(this.x + vx - this.sx, this.y + vy - this.sy);
if (this.distanceTraveled >= this.distanceToTarget) {
explode(this.tx, this.ty, this.hue);
fireworks.splice(i, 1);
} else {
this.x += vx;
this.y += vy;
}
}
draw() {
ctx.beginPath();
const last = this.trail[0] || [this.x, this.y];
ctx.moveTo(last[0], last[1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = hsl(this.hue, 100, this.brightness);
ctx.lineWidth = 2;
ctx.stroke();
}
}
class Particle {
constructor(x, y, hue) {
this.x = x;
this.y = y;
this.hue = rand(hue - 20, hue + 20);
this.angle = rand(0, Math.PI * 2);
this.speed = rand(1, 10);
this.friction = 0.96;
this.gravity = 0.06;
this.alpha = 1;
this.decay = rand(0.008, 0.02);
this.brightness = rand(50, 80);
this.trail = [];
this.trailLength = 8;
}
update(i) {
this.trail.push([this.x, this.y]);
if (this.trail.length > this.trailLength) this.trail.shift();
this.speed *= this.friction;
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed + this.gravity;
this.alpha -= this.decay;
if (this.alpha <= 0) particles.splice(i, 1);
}
draw() {
ctx.beginPath();
const last = this.trail[0] || [this.x, this.y];
ctx.moveTo(last[0], last[1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = hsl(this.hue, 100, this.brightness, this.alpha);
ctx.lineWidth = 1.5;
ctx.stroke();
}
}
function explode(x, y, hue) {
const count = Math.floor(rand(40, 90));
for (let i = 0; i < count; i++) {
particles.push(new Particle(x, y, hue));
}
}
function launch(x = rand(w * 0.1 / devicePixelRatio, w * 0.9 / devicePixelRatio), y = rand(50, h * 0.5 / devicePixelRatio)) {
const sx = rand(0.2, 0.8) * (w / devicePixelRatio);
const sy = h / devicePixelRatio;
fireworks.push(new Firework(sx, sy, x, y));
}
function loop() {
if (!running) return;
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fillRect(0, 0, w / devicePixelRatio, h / devicePixelRatio);
ctx.globalCompositeOperation = 'lighter';
for (let i = fireworks.length - 1; i >= 0; i--) {
fireworks[i].draw();
fireworks[i].update(i);
}
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].draw();
particles[i].update(i);
}
rafId = requestAnimationFrame(loop);
}
function onResize() {
w = canvas.width = window.innerWidth * devicePixelRatio;
h = canvas.height = window.innerHeight * devicePixelRatio;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(devicePixelRatio, devicePixelRatio);
}
function onClick(e) {
// 点击位置放烟花
launch(e.clientX, e.clientY);
}
function onKeydown(e) {
if (e.key === 'Escape') {
cleanup();
}
}
function cleanup() {
running = false;
cancelAnimationFrame(rafId);
clearInterval(autoLaunchTimer);
window.removeEventListener('resize', onResize);
window.removeEventListener('click', onClick);
window.removeEventListener('keydown', onKeydown);
canvas.remove();
delete window.__fireworksCleanup__;
console.log('🎆 Fireworks stopped and removed.');
}
window.__fireworksCleanup__ = cleanup;
window.addEventListener('resize', onResize);
window.addEventListener('click', onClick);
window.addEventListener('keydown', onKeydown);
// 自动发射
autoLaunchTimer = setInterval(() => {
launch();
if (Math.random() < 0.35) setTimeout(() => launch(), rand(80, 250));
if (Math.random() < 0.2) setTimeout(() => launch(), rand(120, 400));
}, 500);
// 初始几发
for (let i = 0; i < 3; i++) setTimeout(() => launch(), i * 180);
loop();
console.log('🎆 Fireworks running! Click anywhere to launch. Press Esc to stop.');
})();
在开发者界面console复制上面的代码运行即可