import type { ParticleConfig } from "@/types/config"; import { particleConfig } from "@/config"; const BOUNDARY_OFFSET = 100; // 粒子对象类 class Particle { x: number; y: number; s: number; r: number; a: number; fn: { x: (x: number, y: number) => number; y: (x: number, y: number) => number; r: (r: number) => number; a: (a: number) => number; }; idx: number; img: HTMLImageElement; limitArray: number[]; config: ParticleConfig; // 构造函数 constructor( x: number, y: number, s: number, r: number, a: number, fn: { x: (x: number, y: number) => number; y: (x: number, y: number) => number; r: (r: number) => number; a: (a: number) => number; }, idx: number, img: HTMLImageElement, limitArray: number[], config: ParticleConfig, ) { this.x = x; this.y = y; this.s = s; this.r = r; this.a = a; this.fn = fn; this.idx = idx; this.img = img; this.limitArray = limitArray; this.config = config; } // 绘制粒子 draw(cxt: CanvasRenderingContext2D) { cxt.save(); cxt.translate(this.x, this.y); cxt.rotate(this.r); cxt.globalAlpha = this.a; cxt.drawImage(this.img, 0, 0, 40 * this.s, 40 * this.s); cxt.restore(); } // 更新粒子位置和状态 update() { this.x = this.fn.x(this.x, this.y); this.y = this.fn.y(this.y, this.y); this.r = this.fn.r(this.r); this.a = this.fn.a(this.a); // 如果粒子越界或完全透明,重新调整位置 if ( this.x > window.innerWidth || this.x < 0 || this.y > window.innerHeight + BOUNDARY_OFFSET || this.y < -BOUNDARY_OFFSET || // 从顶部消失 this.a <= 0 ) { // 如果粒子不做限制 if (this.limitArray[this.idx] === -1) { this.resetPosition(); } // 否则粒子有限制 else { if (this.limitArray[this.idx] > 0) { this.resetPosition(); this.limitArray[this.idx]--; } } } } // 重置粒子位置 private resetPosition() { this.r = getRandom("fnr", this.config); if (Math.random() > 0.4) { this.x = getRandom("x", this.config); this.y = window.innerHeight + Math.random() * BOUNDARY_OFFSET; // 从屏幕底部开始 this.s = getRandom("s", this.config); this.r = getRandom("r", this.config); this.a = getRandom('a', this.config); } else { this.x = window.innerWidth; this.y = getRandom("y", this.config); this.s = getRandom("s", this.config); this.r = getRandom("r", this.config); this.a = getRandom('a', this.config); } } } // 粒子列表类 class ParticleList { list: Particle[]; // 构造函数 constructor() { this.list = []; } // 添加粒子 push(particle: Particle) { this.list.push(particle); } // 更新所有粒子 update() { for (let i = 0, len = this.list.length; i < len; i++) { this.list[i].update(); } } // 绘制所有粒子 draw(cxt: CanvasRenderingContext2D) { for (let i = 0, len = this.list.length; i < len; i++) { this.list[i].draw(cxt); } } // 获取指定索引的粒子 get(i: number) { return this.list[i]; } // 获取粒子数量 size() { return this.list.length; } } // 获取随机值的函数 function getRandom(option: string, config: ParticleConfig): any { let ret: any; let random: number; // 根据选项获取随机值 switch (option) { case "x": ret = Math.random() * window.innerWidth; break; case "y": ret = window.innerHeight + Math.random() * BOUNDARY_OFFSET; // 初始位置在屏幕底部 break; case "s": ret = config.size.min + Math.random() * (config.size.max - config.size.min); break; case "r": ret = Math.random() * 6; break; case "a": ret = config.opacity.min + Math.random() * (config.opacity.max - config.opacity.min); break; case "fnx": random = config.speed.horizontal.min + Math.random() * (config.speed.horizontal.max - config.speed.horizontal.min); // x方向保持较小的随机运动 ret = function (x: number, y: number) { return x + random; }; break; case "fny": random = -(config.speed.vertical.min + Math.random() * (config.speed.vertical.max - config.speed.vertical.min)); // y方向随机向上运动 ret = function (x: number, y: number) { return y + random; }; break; case "fnr": ret = function (r: number) { return r + config.speed.rotation * 0.1; }; break; case "fna": ret = function (alpha: number) { return alpha - config.speed.fadeSpeed * 0.01; }; break; } return ret; } // 粒子管理器类 export class ParticleManager { private config: ParticleConfig; private canvas: HTMLCanvasElement | null = null; private ctx: CanvasRenderingContext2D | null = null; private particleList: ParticleList | null = null; private animationId: number | null = null; private img: HTMLImageElement | null = null; private isRunning = false; // 构造函数 constructor(config: ParticleConfig) { this.config = config; } // 初始化粒子特效 async init(): Promise { if (typeof document === "undefined" || !this.config.enable || this.isRunning) { return; } // 创建图片对象 this.img = new Image(); this.img.src = "/assets/images/particle.png"; // 使用粒子图片 // 等待图片加载完成 await new Promise((resolve, reject) => { if (this.img) { this.img.onload = () => resolve(); this.img.onerror = () => reject(new Error("Failed to load particle image")); } }); // 创建画布 this.createCanvas(); // 创建粒子列表 this.createParticleList(); // 启动动画循环 this.startAnimation(); // 标记为运行中 this.isRunning = true; } // 创建画布 private createCanvas(): void { if (typeof document === "undefined") return; this.canvas = document.createElement("canvas"); this.canvas.height = window.innerHeight; this.canvas.width = window.innerWidth; this.canvas.setAttribute( "style", `position: fixed; left: 0; top: 0; pointer-events: none; z-index: ${this.config.zIndex};`, ); this.canvas.setAttribute("id", "canvas_particle"); document.body.appendChild(this.canvas); this.ctx = this.canvas.getContext("2d"); // 监听窗口大小变化 if (typeof window !== "undefined") { window.addEventListener("resize", this.handleResize.bind(this)); } } // 创建粒子列表 private createParticleList(): void { if (!this.img || !this.ctx) return; this.particleList = new ParticleList(); const limitArray = new Array(this.config.particleNum).fill( this.config.limitTimes, ); for (let i = 0; i < this.config.particleNum; i++) { const randomX = getRandom("x", this.config); const randomY = getRandom("y", this.config); const randomS = getRandom("s", this.config); const randomR = getRandom("r", this.config); const randomA = getRandom("a", this.config); const randomFnx = getRandom("fnx", this.config); const randomFny = getRandom("fny", this.config); const randomFnR = getRandom("fnr", this.config); const randomFnA = getRandom("fna", this.config); const particle = new Particle( randomX, randomY, randomS, randomR, randomA, { x: randomFnx, y: randomFny, r: randomFnR, a: randomFnA, }, i, this.img, limitArray, this.config, ); particle.draw(this.ctx); this.particleList.push(particle); } } // 开始动画 private startAnimation(): void { if (!this.ctx || !this.canvas || !this.particleList) return; const animate = () => { if (!this.ctx || !this.canvas || !this.particleList) return; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.particleList.update(); this.particleList.draw(this.ctx); this.animationId = requestAnimationFrame(animate); }; this.animationId = requestAnimationFrame(animate); } // 处理窗口大小变化 private handleResize(): void { if (this.canvas) { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; } } // 停止粒子特效 stop(): void { if (this.animationId && typeof window !== "undefined") { cancelAnimationFrame(this.animationId); this.animationId = null; } if (this.canvas && typeof document !== "undefined") { document.body.removeChild(this.canvas); this.canvas = null; } if (typeof window !== "undefined") { window.removeEventListener("resize", this.handleResize.bind(this)); } this.isRunning = false; } // 切换粒子特效 toggle(): void { if (this.isRunning) { this.stop(); } else { this.init(); } } // 更新配置 updateConfig(newConfig: ParticleConfig): void { const wasRunning = this.isRunning; if (wasRunning) { this.stop(); } this.config = newConfig; if (wasRunning && newConfig.enable) { this.init(); } } // 获取运行状态 getIsRunning(): boolean { return this.isRunning; } } // 创建全局粒子管理器实例 let globalParticleManager: ParticleManager | null = null; // 初始化粒子特效 export function initParticle(config: ParticleConfig): void { if (globalParticleManager) { globalParticleManager.updateConfig(config); } else { globalParticleManager = new ParticleManager(config); if (config.enable) { globalParticleManager.init(); } } } // 切换粒子特效 export function toggleParticle(): void { if (globalParticleManager) { globalParticleManager.toggle(); } } // 停止粒子特效 export function stopParticle(): void { if (globalParticleManager) { globalParticleManager.stop(); globalParticleManager = null; } } // 获取粒子特效运行状态 export function getParticleStatus(): boolean { return globalParticleManager ? globalParticleManager.getIsRunning() : false; } // 包含配置检查、重复初始化检查以及页面加载状态处理 export function setupParticleEffects(): void { if (typeof window === "undefined") return; // 初始化函数 const init = () => { if (!particleConfig || !particleConfig.enable) return; if ((window as any).particleInitialized) return; initParticle(particleConfig); (window as any).particleInitialized = true; }; // 处理页面加载状态 if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } }