114 lines
3.3 KiB
JavaScript
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 };
|
|
})();
|