console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>粒子跳动爱心</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background: #000;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="heartCanvas"></canvas>
<script>
const canvas = document.getElementById('heartCanvas');
const ctx = canvas.getContext('2d');
// 画布自适应窗口大小
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
let points = [];
let edgePoints = [];
let centerPoints = [];
let haloPoints = [];
const enlarge = 11;
// 爱心函数
function heartFunc(t) {
let x = 16 * Math.pow(Math.sin(t), 3);
let y = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
x *= enlarge;
y *= enlarge;
x += canvas.width / 2;
y += canvas.height / 2;
return { x, y }
}
// 内部随机扩散
function scatter(x, y, beta) {
let rx = -beta * Math.log(Math.random());
let ry = -beta * Math.log(Math.random());
let dx = rx * (x - canvas.width / 2);
let dy = ry * (y - canvas.height / 2);
return { x: x - dx, y: y - dy }
}
function shrinkPoint(x, y, ratio) {
let dis = Math.pow(Math.pow(x - canvas.width / 2, 2) + Math.pow(y - canvas.height / 2, 2), 0.6);
let force = -1 / dis;
let dx = ratio * force * (x - canvas.width / 2);
let dy = ratio * force * (y - canvas.height / 2);
return { x: x - dx, y: y - dy }
}
function curve(p) {
return 2 * (2 * Math.sin(4 * p)) / (2 * Math.PI)
}
// 构建爱心粒子
function build() {
for (let i = 0; i < 2000; i++) {
let t = Math.random() * 2 * Math.PI;
let p = heartFunc(t);
points.push(p);
}
for (let p of points) {
for (let j = 0; j < 3; j++) {
let np = scatter(p.x, p.y, 0.05);
edgePoints.push(np);
}
}
let plist = [...points];
for (let i = 0; i < 4000; i++) {
let p = plist[Math.floor(Math.random() * plist.length)];
let np = scatter(p.x, p.y, 0.27);
centerPoints.push(np);
}
}
build();
let frame = 0;
function calcPos(x, y, ratio) {
let dis = Math.pow(Math.pow(x - canvas.width / 2, 2) + Math.pow(y - canvas.height / 2, 2), 0.42);
let force = 1 / dis;
let dx = ratio * force * (x - canvas.width / 2) + (Math.random() > 0.5 ? 1 : -1);
let dy = ratio * force * (y - canvas.height / 2) + (Math.random() > 0.5 ? 1 : -1);
return { x: x - dx, y: y - dy }
}
// 动画循环
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let rat = 10 * curve(frame / 10 * Math.PI);
let haloR = 4 + 6 * (1 + curve(frame / 10 * Math.PI));
let haloNum = 3000 + 4000 * Math.pow(Math.abs(curve(frame / 10 * Math.PI)), 2);
haloPoints = [];
for (let i = 0; i < haloNum; i++) {
let t = Math.random() * 2 * Math.PI;
let p = heartFunc(t, 11.6);
let sp = shrinkPoint(p.x, p.y, haloR);
sp.x += Math.floor(Math.random() * 28 - 14);
sp.y += Math.floor(Math.random() * 28 - 14);
haloPoints.push(sp);
}
// 绘制所有粒子,粉色 #ff88bb
for (let p of haloPoints) {
let np = calcPos(p.x, p.y, rat);
ctx.fillStyle = "#ff88bb";
ctx.fillRect(np.x, np.y, 2, 2);
}
for (let p of points) {
let np = calcPos(p.x, p.y, rat);
ctx.fillStyle = "#ff88bb";
ctx.fillRect(np.x, np.y, 2, 2);
}
for (let p of edgePoints) {
let np = calcPos(p.x, p.y, rat);
ctx.fillStyle = "#ff88bb";
ctx.fillRect(np.x, np.y, 2, 2);
}
for (let p of centerPoints) {
let np = calcPos(p.x, p.y, rat);
ctx.fillStyle = "#ff88bb";
ctx.fillRect(np.x, np.y, 2, 2);
}
frame++;
requestAnimationFrame(draw);
}
draw();
</script>
</body>
</html>