返回主页
景安游乐场 · 星河粒子 (Interactive Starfield)
内置 HTML5 Canvas 物理引擎。粒子在夜空中自由游移;鼠标滑过时,引力波驱动粒子平滑向核心聚拢;点击时触发粉紫色星海火花四射,微重力飘散。
💻 仅代码
🌓 左右分屏
👁️ 仅看特效
运行调试
实时源码编辑器 (HTML/CSS/JS)
支持 Tab 缩进
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>星河粒子 · 独立调试版</title> <style> body, html { margin: 0; padding: 0; width: 100%; height: 100%; background: #0f0a1d; overflow: hidden; font-family: system-ui, -apple-system, sans-serif; } .effect-container { width: 100%; height: 100%; position: relative; background: radial-gradient(circle at center, #221338 0%, #0f0a1d 100%); display: flex; justify-content: center; align-items: center; cursor: pointer; } canvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; } /* 提示语 */ .interaction-hint { position: absolute; text-align: center; color: rgba(255, 255, 255, 0.45); font-size: 15px; font-weight: 300; letter-spacing: 0.3em; text-transform: uppercase; text-shadow: 0 0 10px rgba(182, 115, 216, 0.3); pointer-events: none; user-select: none; z-index: 5; } </style> </head> <body> <div class="effect-container" id="interaction-zone"> <div class="interaction-hint">引力潮起 · 触碰指尖星芒</div> <canvas id="particle-canvas"></canvas> </div> <script> const zone = document.getElementById("interaction-zone"); const canvas = document.getElementById("particle-canvas"); const ctx = canvas.getContext("2d"); let particles = []; let mouse = { x: null, y: null, active: false }; const maxParticles = 120; // 满屏,增加粒子数量 function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resizeCanvas(); window.addEventListener("resize", resizeCanvas); class Particle { constructor(isSpark = false, clickX = 0, clickY = 0) { this.isSpark = isSpark; if (isSpark) { this.x = clickX; this.y = clickY; const angle = Math.random() * Math.PI * 2; const speed = Math.random() * 4 + 1.5; this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed; this.size = Math.random() * 4 + 1.5; this.color = "hsla(" + (260 + Math.random() * 60) + ", 100%, 75%, " + (Math.random() * 0.4 + 0.6) + ")"; this.alpha = 1; this.decay = Math.random() * 0.015 + 0.01; } else { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 0.6; this.vy = (Math.random() - 0.5) * 0.6; this.size = Math.random() * 2.5 + 0.5; this.color = "hsla(" + (270 + Math.random() * 40) + ", 80%, 80%, " + (Math.random() * 0.3 + 0.2) + ")"; this.alpha = Math.random() * 0.5 + 0.2; } } update() { if (this.isSpark) { this.x += this.vx; this.y += this.vy; this.vy += 0.03; // 微小重力 this.alpha -= this.decay; } else { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; if (mouse.active && mouse.x !== null && mouse.y !== null) { const dx = mouse.x - this.x; const dy = mouse.y - this.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 150) { // 引力半径加大 const force = (150 - dist) / 150; this.x += (dx / dist) * force * 1.2; this.y += (dy / dist) * force * 1.2; } } } } draw() { ctx.save(); ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fillStyle = this.color; ctx.globalAlpha = this.isSpark ? Math.max(0, this.alpha) : this.alpha; ctx.fill(); ctx.restore(); } } function initParticles() { particles = []; for (let i = 0; i < maxParticles; i++) { particles.push(new Particle()); } } initParticles(); function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles = particles.filter(p => !p.isSpark || p.alpha > 0); const normalCount = particles.filter(p => !p.isSpark).length; if (normalCount < maxParticles) { particles.push(new Particle()); } particles.forEach(p => { p.update(); p.draw(); }); requestAnimationFrame(animate); } animate(); zone.addEventListener("mousemove", (e) => { const rect = zone.getBoundingClientRect(); mouse.x = e.clientX - rect.left; mouse.y = e.clientY - rect.top; mouse.active = true; }); zone.addEventListener("mouseleave", () => { mouse.x = null; mouse.y = null; mouse.active = false; }); zone.addEventListener("mousedown", (e) => { const rect = zone.getBoundingClientRect(); const clickX = e.clientX - rect.left; const clickY = e.clientY - rect.top; for (let i = 0; i < 25; i++) { // 更多火花 particles.push(new Particle(true, clickX, clickY)); } }); </script> </body> </html>
实时渲染沙盒视窗 (Sandboxed Sandbox)
实时可交互