Files

114 lines
3.3 KiB
JavaScript

/**
* 文件功能:聊天室爱心飘落特效
*
* 在透明 Canvas 上绘制成组心形,从上方向下飘落并轻微摇摆,
* 适用于表白、婚礼、节日祝福等氛围场景。
*/
const HeartsEffect = (() => {
class Heart {
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 : -24;
this.size = Math.random() * 10 + 10;
this.speedY = Math.random() * 1.5 + 1.1;
this.swing = Math.random() * Math.PI * 2;
this.swingSpeed = Math.random() * 0.04 + 0.015;
this.rotation = (Math.random() - 0.5) * 0.35;
this.rotationSpeed = (Math.random() - 0.5) * 0.01;
this.alpha = Math.random() * 0.28 + 0.62;
this.color = ["#fb7185", "#f43f5e", "#ec4899", "#fda4af"][
Math.floor(Math.random() * 4)
];
}
/**
* 更新爱心位置。
*/
update() {
this.swing += this.swingSpeed;
this.rotation += this.rotationSpeed;
this.x += Math.sin(this.swing) * 1.1;
this.y += this.speedY;
if (this.y > this.h + 28) {
this.reset(false);
}
}
/**
* 绘制爱心形状。
*
* @param {CanvasRenderingContext2D} ctx
*/
draw(ctx) {
const s = this.size;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.scale(s / 16, s / 16);
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
ctx.shadowColor = "rgba(244, 63, 94, 0.45)";
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.moveTo(0, -4);
ctx.bezierCurveTo(12, -18, 26, -2, 0, 18);
ctx.bezierCurveTo(-26, -2, -12, -18, 0, -4);
ctx.fill();
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 = 9000;
const HEART_COUNT = Math.min(48, Math.max(24, Math.floor(w / 34)));
const hearts = Array.from({ length: HEART_COUNT }, () => new Heart(w, h));
const startTime = performance.now();
let animId = null;
function animate(now) {
ctx.clearRect(0, 0, w, h);
hearts.forEach((heart) => {
heart.update();
heart.draw(ctx);
});
if (now - startTime < DURATION) {
animId = requestAnimationFrame(animate);
} else {
cancelAnimationFrame(animId);
ctx.clearRect(0, 0, w, h);
onEnd();
}
}
animId = requestAnimationFrame(animate);
}
return { start };
})();