<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Heart</title><style>body {margin: 0;overflow: hidden;background-color: #1a001a;}canvas {display: block;}</style></head><body><canvas id="heartCanvas"></canvas><script>const canvas = document.getElementById("heartCanvas");const ctx = canvas.getContext("2d");canvas.width = window.innerWidth;canvas.height = window.innerHeight;const BG_COLOR = "#1a001a";const PARTICLE_COUNT = 600;const HEART_SCALE = 20;const ANIMATION_DURATION = 360; // Number of framesconst COLORS = ["#ffc0cb","#ff69b4","#db7093","#e6e6fa","#dda0dd","#ffffff",];class Particle {constructor() {const t = Math.random() * 2 * Math.PI;this.targetX = HEART_SCALE * (16 * Math.pow(Math.sin(t), 3));this.targetY =HEART_SCALE *(13 * Math.cos(t) -5 * Math.cos(2 * t) -2 * Math.cos(3 * t) -Math.cos(4 * t));// Center the heartthis.targetX += canvas.width / 2;this.targetY = -this.targetY + canvas.height / 2; // Y is inverted in canvasthis.startX = Math.random() * canvas.width;this.startY = Math.random() * canvas.height;this.x = this.startX;this.y = this.startY;this.dx = (this.targetX - this.startX) / ANIMATION_DURATION;this.dy = (this.targetY - this.startY) / ANIMATION_DURATION;this.vx = 0;this.vy = 0;this.size = Math.random() * 2 + 1;this.color = COLORS[Math.floor(Math.random() * COLORS.length)];}update(mouseX, mouseY) {if (frame < ANIMATION_DURATION) {// Initial formation animationthis.x += this.dx;this.y += this.dy;} else {// Move towards target positionconst dxTarget = this.targetX - this.x;const dyTarget = this.targetY - this.y;const distanceTarget = Math.sqrt(dxTarget * dxTarget + dyTarget * dyTarget);// Apply a force to return to the targetif (distanceTarget > 0.1) {this.vx += dxTarget * 0.001;this.vy += dyTarget * 0.001;}// Repulsion from mouseconst dxMouse = this.x - mouseX;const dyMouse = this.y - mouseY;const distanceMouse = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);const repulsionRadius = 60;if (distanceMouse < repulsionRadius) {const force = (repulsionRadius - distanceMouse) / repulsionRadius;this.vx += (dxMouse / distanceMouse) * force * 0.5;this.vy += (dyMouse / distanceMouse) * force * 0.5;}// Apply friction/dampingthis.vx *= 0.95;this.vy *= 0.95;// Update positionthis.x += this.vx;this.y += this.vy;}}draw() {ctx.beginPath();ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);ctx.fillStyle = this.color;ctx.fill();}}const particles = [];for (let i = 0; i < PARTICLE_COUNT; i++) {particles.push(new Particle());}let frame = 0;let mouseX = -1000;let mouseY = -1000;function animate() {ctx.fillStyle = BG_COLOR;ctx.fillRect(0, 0, canvas.width, canvas.height);particles.forEach((p) => {p.update(mouseX, mouseY);p.draw();});if (frame < ANIMATION_DURATION) {frame++;} else {// Final draw is now part of the continuous loop// Add text when animation is "done"ctx.fillStyle = "white";ctx.font = "italic 16px Arial";ctx.textAlign = "center";ctx.fillText("Made with Love",canvas.width / 2,canvas.height / 2 - 10);ctx.fillText("For TQW, My Dear",canvas.width / 2,canvas.height / 2 + 10);}requestAnimationFrame(animate);}animate();window.addEventListener("mousemove", (e) => {mouseX = e.clientX;mouseY = e.clientY;});window.addEventListener("mouseout", () => {mouseX = -1000;mouseY = -1000;});window.addEventListener("resize", () => {canvas.width = window.innerWidth;canvas.height = window.innerHeight;// Re-center particles on resizeparticles.forEach((p) => {const t = Math.random() * 2 * Math.PI;p.targetX = HEART_SCALE * (16 * Math.pow(Math.sin(t), 3));p.targetY =HEART_SCALE *(13 * Math.cos(t) -5 * Math.cos(2 * t) -2 * Math.cos(3 * t) -Math.cos(4 * t));p.targetX += canvas.width / 2;p.targetY = -p.targetY + canvas.height / 2;});});</script></body></html>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。