/** * 文件功能:聊天室樱花飘落特效 * * 使用 Canvas 绘制多层粉色花瓣,让花瓣在屏幕上方生成后缓慢下落、 * 左右摆动并带轻微旋转,营造柔和的春日氛围。 */ const SakuraEffect = (() => { class Petal { constructor(w, h) { this.w = w; this.h = h; this.reset(true); } /** * 重置花瓣位置与运动参数。 * * @param {boolean} initial 是否首次初始化 */ reset(initial = false) { this.x = Math.random() * this.w; this.y = initial ? Math.random() * this.h : -20; this.size = Math.random() * 7 + 8; this.speedY = Math.random() * 1.1 + 0.7; this.speedX = Math.random() * 0.6 - 0.3; this.swing = Math.random() * Math.PI * 2; this.swingSpeed = Math.random() * 0.03 + 0.01; this.rotation = Math.random() * Math.PI * 2; this.rotationSpeed = (Math.random() - 0.5) * 0.03; this.alpha = Math.random() * 0.25 + 0.55; this.color = ["#fbcfe8", "#f9a8d4", "#fda4af", "#fce7f3"][ Math.floor(Math.random() * 4) ]; } /** * 更新花瓣运动状态。 */ update() { this.swing += this.swingSpeed; this.rotation += this.rotationSpeed; this.x += this.speedX + Math.sin(this.swing) * 0.75; this.y += this.speedY; if (this.y > this.h + 30 || this.x < -30 || this.x > this.w + 30) { this.reset(false); } } /** * 绘制单片樱花花瓣。 * * @param {CanvasRenderingContext2D} ctx */ draw(ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.rotation); ctx.globalAlpha = this.alpha; ctx.fillStyle = this.color; ctx.shadowColor = "rgba(244, 114, 182, 0.35)"; ctx.shadowBlur = 8; ctx.beginPath(); ctx.moveTo(0, -this.size * 0.9); ctx.quadraticCurveTo(this.size * 0.9, -this.size * 0.25, 0, this.size); ctx.quadraticCurveTo(-this.size * 0.9, -this.size * 0.25, 0, -this.size * 0.9); ctx.fill(); ctx.strokeStyle = "rgba(190, 24, 93, 0.25)"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, -this.size * 0.55); ctx.lineTo(0, this.size * 0.7); ctx.stroke(); ctx.restore(); } } /** * 启动樱花特效。 * * @param {HTMLCanvasElement} canvas * @param {Function} onEnd */ function start(canvas, onEnd) { const ctx = canvas.getContext("2d"); const w = canvas.width; const h = canvas.height; const DURATION = 10000; const PETAL_COUNT = Math.min(58, Math.max(34, Math.floor(w / 28))); const petals = Array.from({ length: PETAL_COUNT }, () => new Petal(w, h)); const startTime = performance.now(); let animId = null; function animate(now) { ctx.clearRect(0, 0, w, h); petals.forEach((petal) => { petal.update(); petal.draw(ctx); }); if (now - startTime < DURATION) { animId = requestAnimationFrame(animate); } else { cancelAnimationFrame(animId); ctx.clearRect(0, 0, w, h); onEnd(); } } animId = requestAnimationFrame(animate); } return { start }; })();