1、创建heart.js文件
// 定义画布的宽度和高度,以及画布的中心点坐标
const CANVAS_WIDTH = 640;
const CANVAS_HEIGHT = 480;
const CANVAS_CENTER_X = CANVAS_WIDTH / 2;
const CANVAS_CENTER_Y = CANVAS_HEIGHT / 2;
// 定义图像的放大因子,用于调整爱心的大小
const IMAGE_ENLARGE_FACTOR = 11;
// 定义爱心的颜色
const HEART_COLOR = "#FF69B4";
// 生成爱心上的一个点的坐标
function generateHeartCoordinate(t, shrinkRatio = IMAGE_ENLARGE_FACTOR) {
// 使用参数t计算原始的x和y坐标
let x = 16 * (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和y坐标
x *= shrinkRatio;
y *= shrinkRatio;
// 将坐标转换到画布的中心
x += CANVAS_CENTER_X;
y += CANVAS_CENTER_Y;
// 返回调整后的坐标,向下取整以确保坐标为整数
return { x: Math.floor(x), y: Math.floor(y) };
}
// 绘制爱心的函数
function drawHeart(context, ratio, frame) {
// 清空画布
context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// 绘制爱心的光晕部分
let heartHaloPoints = new Set();
for (let i = 0; i < 3000 + 4000 * Math.abs(customCurve(frame / 10 * Math.PI) ** 2); i++) {
// 生成一个随机的t值,用于计算坐标
let t = Math.random() * 4 * Math.PI;
// 使用generateHeartCoordinate函数计算坐标,并使用一个更大的缩放比例来使光晕更大
let { x, y } = generateHeartCoordinate(t, 11.5);
// 计算一个力的大小,用于调整光晕的扩散效果
let force = -1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.6);
// 根据力的大小调整x和y坐标
x -= ratio * force * (x - CANVAS_CENTER_X);
y -= ratio * force * (y - CANVAS_CENTER_Y);
// 添加一些随机的偏移量,使光晕更加自然
x += Math.floor(Math.random() * 29 - 14);
y += Math.floor(Math.random() * 29 - 14);
// 使用fillRect函数绘制一个2x2的正方形,作为光晕的一个点
context.fillStyle = HEART_COLOR;
context.fillRect(x, y, 2, 2);
}
// 绘制爱心的主体部分
let allPoints = []; // 存储所有点的数组
let originalPoints = new Set(); // 存储原始点的集合
let edgeDiffusionPoints = new Set(); // 存储边缘扩散点的集合
let centerDiffusionPoints = new Set(); // 存储中心扩散点的集合
// 生成原始点
for (let i = 0; i < 2000; i++) {
let t = Math.random() * 2 * Math.PI;
let { x, y } = generateHeartCoordinate(t);
originalPoints.add({ x, y });
}
// 从原始点生成边缘扩散点
originalPoints.forEach(({ x, y }) => {
for (let i = 0; i < 3; i++) {
let newX = x, newY = y;
newX += Math.random() * 5 - 2.5;
newY += Math.random() * 5 - 2.5;
edgeDiffusionPoints.add({ x: newX, y: newY });
}
});
// 生成中心扩散点
for (let i = 0; i < 6000; i++) {
let { x, y } = Array.from(originalPoints)[Math.floor(Math.random() * originalPoints.size)];
let newX = x, newY = y;
newX += Math.random() * 15 - 7.5;
newY += Math.random() * 15 - 7.5;
centerDiffusionPoints.add({ x: newX, y: newY });
}
// 根据力的大小调整所有点的坐标,并将它们添加到allPoints数组中
originalPoints.forEach(({ x, y }) => {
let newX = x, newY = y;
let force = 1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.52);
newX -= ratio * force * (x - CANVAS_CENTER_X);
newY -= ratio * force * (y - CANVAS_CENTER_Y);
allPoints.push({ x: Math.floor(newX), y: Math.floor(newY), size: Math.floor(Math.random() * 3 + 1) });
});
edgeDiffusionPoints.forEach(({ x, y }) => {
let newX = x, newY = y;
let force = 1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.52);
newX -= ratio * force * (x - CANVAS_CENTER_X);
newY -= ratio * force * (y - CANVAS_CENTER_Y);
allPoints.push({ x: Math.floor(newX), y: Math.floor(newY), size: Math.floor(Math.random() * 2 + 1) });
});
centerDiffusionPoints.forEach(({ x, y }) => {
let newX = x, newY = y;
let force = 1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.52);
newX -= ratio * force * (x - CANVAS_CENTER_X);
newY -= ratio * force * (y - CANVAS_CENTER_Y);
allPoints.push({ x: Math.floor(newX), y: Math.floor(newY), size: Math.floor(Math.random() * 2 + 1) });
});
// 绘制所有点
allPoints.forEach(({ x, y, size }) => {
context.fillStyle = HEART_COLOR;
context.fillRect(x, y, size, size);
});
// 添加文本 "stormsha" 到爱心中间
context.font = "bold 24px Arial";
context.fillStyle = HEART_COLOR;
context.textAlign = "center";
context.fillText("stormsha", CANVAS_CENTER_X, CANVAS_CENTER_Y);
}
// 定义一个自定义的曲线函数,用于调整爱心的跳动效果
function customCurve(p) {
return 2 * (2 * Math.sin(4 * p)) / (2 * Math.PI);
}
// 当文档加载完成时,开始动画
document.addEventListener("DOMContentLoaded", () => {
// 获取画布和2d上下文
const canvas = document.getElementById("heartCanvas");
const context = canvas.getContext("2d");
// 定义一些变量,用于控制动画
let ratio = 0; // 力的大小比例
let frame = 0; // 当前帧数
// 动画函数
function animate() {
// 根据当前帧数计算力的大小比例
ratio = 10 * customCurve(frame / 10 * Math.PI);
// 调用drawHeart函数绘制爱心
drawHeart(context, ratio, frame);
// 增加帧数
frame++;
// 请求下一帧的动画
requestAnimationFrame(animate);
}
// 开始动画
animate();
});
这段代码实现了一个跳动的爱心动画。首先,它定义了一些常量,如画布的尺寸和爱心的颜色。然后,它定义了一个generateHeartCoordinate
函数,用于生成爱心上的一个点的坐标。
接下来,它定义了一个drawHeart
函数,用于绘制爱心。这个函数首先清空画布,然后绘制爱心的光晕部分,再绘制爱心的主体部分。最后,它添加了一些文本到爱心中间。
在文档加载完成后,代码会获取画布和2d上下文,并定义一些变量,如力的大小比例和当前帧数。然后,它会调用animate
函数开始动画。
这个函数会根据当前帧数计算力的大小比例,然后调用drawHeart
函数绘制爱心,并请求下一帧的动画。通过不断调用animate函数,就可以实现爱心的跳动效果。
2、创建love.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"> <!-- 指定文档使用UTF-8字符编码 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 设置视口,以适应不同设备屏幕大小 -->
<title>Beating Heart</title> <!-- 文档标题 -->
<style>
/* 样式表定义 */
body {
background-color: black; /* 设置页面背景颜色为黑色 */
margin: 0; /* 去除页面默认边距 */
overflow: hidden; /* 隐藏超出画布的内容 */
}
canvas {
display: block; /* 将画布作为块级元素显示 */
margin: auto; /* 使画布在页面中居中 */
}
.text {
position: absolute; /* 将文本绝对定位 */
top: 50%; /* 使文本距离页面顶部50% */
left: 50%; /* 使文本距离页面左侧50% */
transform: translate(-50%, -50%); /* 将文本向左、向上平移自身宽度和高度的一半,实现在页面中居中 */
color: #FF69B4; /* 设置文本颜色为浅粉色 */
font-family: '宋体'; /* 设置文本字体为宋体 */
font-size: 18px; /* 设置文本大小为18像素 */
text-align: center; /* 使文本内容居中 */
}
</style>
</head>
<body>
<!-- 页面内容定义 -->
<canvas id="heartCanvas" width="640" height="480"></canvas> <!-- 创建一个宽640像素、高480像素的画布,并设置其ID为heartCanvas -->
<div class="text" style="top: 5%;">点燃我 温暖你</div> <!-- 创建一个文本元素,内容为“点燃我 温暖你”,并设置其样式类为text,距离页面顶部5% -->
<script src="heart.js"></script> <!-- 引入heart.js脚本文件 -->
</body>
</html>
3、总结
把 heart.js
、love.html
文件放在同一个文件夹下,用浏览器打开 love.html
文件即可看到效果