Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

276 lines
9.5 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>26 - 金色粒子爆炸</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: #000;
font-family: Arial, sans-serif;
}
canvas { display: block; }
#ui {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none; z-index: 10;
}
#title {
position: absolute;
top: 25px; left: 30px;
color: #ffd700;
font-size: 16px;
letter-spacing: 4px;
text-shadow: 0 0 10px #ffaa00;
}
#fps {
position: absolute;
top: 20px; right: 25px;
color: #888;
font-size: 12px;
}
#instruction {
position: absolute;
bottom: 30px; left: 50%;
transform: translateX(-50%);
color: #aaa;
font-size: 12px;
letter-spacing: 2px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="ui">
<div id="title">26 - 金色粒子爆炸</div>
<div id="fps">FPS: 60</div>
<div id="instruction">点击画布 · 触发爆炸</div>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
// 粒子池
const MAX_PARTICLES = 6000;
const particles = [];
class Particle {
constructor(x, y, type = 'explosion') {
this.reset(x, y, type);
}
reset(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
// 初始方向 - 爆炸向外
const angle = Math.random() * Math.PI * 2;
const speed = type === 'core' ?
Math.random() * 3 + 1 : // 核心慢速
Math.random() * 8 + 2; // 外围快速
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
// 基础属性
this.life = 1.0;
this.decay = type === 'core' ?
0.003 + Math.random() * 0.004 : // 核心活得久
0.008 + Math.random() * 0.012; // 外围衰减快
this.size = type === 'core' ?
2 + Math.random() * 4 : // 核心较大
1 + Math.random() * 3; // 外围较小
this.maxSize = this.size * (2 + Math.random() * 2);
// 颜色 - 金色系
const hue = 35 + Math.random() * 25; // 35-60度,金黄到橙黄
const sat = 80 + Math.random() * 20; // 高饱和
const light = type === 'core' ?
70 + Math.random() * 30 : // 核心亮
50 + Math.random() * 40; // 外围变化
this.hue = hue;
this.sat = sat;
this.light = light;
// 拖尾
this.history = [];
this.historyLength = type === 'core' ? 8 : 5;
}
update() {
// 保存历史位置(拖尾)
this.history.push({ x: this.x, y: this.y });
if (this.history.length > this.historyLength) {
this.history.shift();
}
// 移动
this.x += this.vx;
this.y += this.vy;
// 阻力 - 逐渐减速
this.vx *= 0.985;
this.vy *= 0.985;
// 重力 - 轻微下落
this.vy += 0.02;
// 大小变化 - 先膨胀后收缩
if (this.life > 0.7) {
this.size += 0.15;
} else {
this.size -= 0.08;
}
this.size = Math.max(0.5, Math.min(this.size, this.maxSize));
// 生命衰减
this.life -= this.decay;
return this.life > 0 && this.size > 0.3;
}
draw(ctx) {
const alpha = this.life * (this.type === 'core' ? 1 : 0.9);
// 绘制拖尾
if (this.history.length > 1) {
for (let i = 0; i < this.history.length - 1; i++) {
const p1 = this.history[i];
const p2 = this.history[i + 1];
const trailAlpha = alpha * (i / this.history.length) * 0.5;
ctx.strokeStyle = `hsla(${this.hue}, ${this.sat}%, ${this.light}%, ${trailAlpha})`;
ctx.lineWidth = this.size * (i / this.history.length);
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
}
// 绘制粒子本体 - 多层光晕
for (let i = 3; i >= 0; i--) {
const r = this.size * (1 + i * 0.8);
const a = alpha * (1 - i * 0.25);
const g = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, r);
g.addColorStop(0, `hsla(${this.hue}, ${this.sat}%, ${this.light + 20}%, ${a})`);
g.addColorStop(0.3, `hsla(${this.hue}, ${this.sat}%, ${this.light}%, ${a * 0.7})`);
g.addColorStop(0.7, `hsla(${this.hue}, ${this.sat - 20}%, ${this.light - 10}%, ${a * 0.3})`);
g.addColorStop(1, `hsla(${this.hue}, ${this.sat - 40}%, ${this.light - 20}%, 0)`);
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(this.x, this.y, r, 0, Math.PI * 2);
ctx.fill();
}
}
}
// 爆炸函数
function createExplosion(x, y) {
// 核心粒子 - 金色最亮
for (let i = 0; i < 800; i++) {
if (particles.length < MAX_PARTICLES) {
particles.push(new Particle(x, y, 'core'));
}
}
// 外围粒子 - 更多更散
for (let i = 0; i < 2000; i++) {
if (particles.length < MAX_PARTICLES) {
const offset = (Math.random() - 0.5) * 40;
particles.push(new Particle(x + offset, y + offset, 'outer'));
}
}
// 闪光粒子 - 极小极亮
for (let i = 0; i < 500; i++) {
if (particles.length < MAX_PARTICLES) {
const p = new Particle(x, y, 'spark');
p.vx *= 1.5;
p.vy *= 1.5;
p.size = 0.5 + Math.random();
p.light = 90;
particles.push(p);
}
}
}
// 初始爆炸
function init() {
createExplosion(canvas.width / 2, canvas.height / 2);
}
// 动画循环
let frames = 0;
let lastTime = performance.now();
const fpsEl = document.getElementById('fps');
function animate() {
// 半透明覆盖 - 拖尾效果
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 更新和绘制粒子
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
const alive = p.update();
if (alive) {
p.draw(ctx);
} else {
particles.splice(i, 1);
}
}
// 自动补充爆炸(可选)
if (particles.length < 200 && Math.random() > 0.95) {
createExplosion(
canvas.width * 0.3 + Math.random() * canvas.width * 0.4,
canvas.height * 0.3 + Math.random() * canvas.height * 0.4
);
}
// FPS
frames++;
const now = performance.now();
if (now - lastTime >= 1000) {
fpsEl.textContent = 'FPS: ' + frames + ' | Particles: ' + particles.length;
frames = 0;
lastTime = now;
}
requestAnimationFrame(animate);
}
// 交互
canvas.addEventListener('mousedown', (e) => {
createExplosion(e.clientX, e.clientY);
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const t = e.touches[0];
createExplosion(t.clientX, t.clientY);
}, { passive: false });
init();
animate();
</script>
</body>
</html>