<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>跳动爱心</title>
</head>
<body>
<script>
console.clear();
let canvas, canvasCtx;
let canvasSize = [0, 0], scale = 1;
let state;
requestAnimationFrame(main);
function main() {
canvas = document.createElement('canvas');
document.body.appendChild(canvas);
document.body.style.margin = '0';
canvas.style.display = 'block';
canvasCtx = canvas.getContext('2d');
checkResizeAndInit();
reset();
canvas.addEventListener('mousemove', (e) => {
state.pointer.pos[0] = e.offsetX;
state.pointer.pos[1] = e.offsetY;
});
canvas.addEventListener('click', reset);
window.addEventListener('resize', reset);
requestAnimationFrame(mainLoop);
function mainLoop() {
tick();
requestAnimationFrame(mainLoop);
}
}
function reset() {
state = {
time: 0,
timeDelta: 1 / 60,
pointer: { pos: [0, 0] },
hearts: [],
};
const min = Math.min(canvasSize[0], canvasSize[1]);
const step = min / 13;
const center = [
canvasSize[0] / 2,
canvasSize[1] / 2
];
drawHeart(
center,
min,
Math.PI * state.time * 0,
null, null,
);
state.hearts.length = 0;
for (let y = 0; y < canvasSize[1]; y += step) {
for (let x = 0; x < canvasSize[0]; x += step) {
const isInside = canvasCtx.isPointInPath(x, y);
if (!isInside) continue;
const dist = Math.hypot(x - center[0], y - center[1]);
state.hearts.push({
pos: [
x + step * Math.random() * 0.25,
y + step * Math.random() * 0.25,
],
w: step * (0.25 + 0.75 * (1 - (dist / min))),
a: (Math.random() - 0.5) * Math.PI * 0.5,
timeOffset: (1 - (dist / min)) * 2,
timeScale: 1,
color1: 'rgba(255, 105, 180, 0.36)', // 亮粉色,透明度36%
color2: 'rgba(255, 105, 180, 0.46)', // 亮粉色,透明度46%
});
}
}
}
function tick() {
checkResizeAndInit();
// 设置背景颜色为淡紫色和肉粉色的渐变效果
const gradient = canvasCtx.createLinearGradient(0, 0, canvasSize[0], canvasSize[1]);
gradient.addColorStop(0, '#c1c3f3'); // 淡紫色
gradient.addColorStop(1, '#ffeae5'); // 肉粉色
canvasCtx.fillStyle = gradient;
canvasCtx.fillRect(0, 0, canvasSize[0], canvasSize[1]);
doIt();
state.time += state.timeDelta;
}
function checkResizeAndInit() {
if (
window.innerWidth === canvasSize[0] &&
window.innerHeight === canvasSize[1]
) return;
canvasSize[0] = canvas.width = window.innerWidth;
canvasSize[1] = canvas.height = window.innerHeight;
}
function doIt() {
const { timeDelta } = state;
for (const heart of state.hearts) {
const time = state.time * heart.timeScale + heart.timeOffset;
const s = ((Math.sin(time * Math.PI) + 1) / 2) ** 0.5 * 0.5 + 0.5;
drawHeart(
heart.pos,
heart.w * s,
heart.a,
heart.color1,
heart.color2,
);
}
}
function drawHeart(pos, w, a, color, color2) {
const s = w / 92;
canvasCtx.save();
canvasCtx.translate(pos[0], pos[1]);
canvasCtx.rotate(a);
canvasCtx.translate(0, 12 * s);
// 增强外阴影效果,减小阴影扩散范围
canvasCtx.shadowColor = 'rgba(255, 20, 147, 0.9)'; // 更强的亮粉色阴影
canvasCtx.shadowBlur = 20; // 降低模糊度,使阴影更紧凑
canvasCtx.shadowOffsetX = 5; // 阴影水平偏移,减小范围
canvasCtx.shadowOffsetY = 5; // 阴影垂直偏移,减小范围
canvasCtx.beginPath();
canvasCtx.ellipse(
-14 * s, -16 * s,
25 * s, 32 * s,
Math.PI * -0.25,
Math.PI * 1, Math.PI * 0,
);
canvasCtx.ellipse(
+14 * s, -16 * s,
25 * s, 32 * s,
Math.PI * +0.25,
Math.PI * 1, Math.PI * 0,
);
canvasCtx.quadraticCurveTo(
+14 * s, 20 * s,
0 * s, 32 * s,
);
canvasCtx.closePath();
// 渐变动画:填充颜色从白色到粉色
const t = (Math.sin(state.time * 0.5) + 1) / 2;
const gradient = canvasCtx.createLinearGradient(-w / 2, -w / 2, w / 2, w / 2);
gradient.addColorStop(0, `rgba(255, 255, 255, ${1 - t * 0.5})`);
gradient.addColorStop(1, `rgba(255, 105, 180, ${t * 0.8})`);
canvasCtx.fillStyle = gradient;
canvasCtx.fill();
// 添加更多的白色高光
canvasCtx.shadowColor = 'rgba(255, 255, 255, 0.8)';
canvasCtx.shadowBlur = 10; // 降低高光阴影模糊度
canvasCtx.shadowOffsetX = -3;
canvasCtx.shadowOffsetY = -3;
if (color) {
canvasCtx.strokeStyle = color;
canvasCtx.stroke();
}
if (color2) {
canvasCtx.fillStyle = color2;
canvasCtx.fill();
}
canvasCtx.restore();
}
function rotateByVector(out, a, v, origin, s) {
const rx = v[0];
const ry = v[1];
const x = a[0] - origin[0];
const y = a[1] - origin[1];
out[0] = origin[0] + (x * rx - y * ry) * s;
out[1] = origin[1] + (y * rx + x * ry) * s;
return out;
}
</script>
</body>
</html>