目录
系列文章
写在前面
完整代码
代码分析
写在后面
系列文章
序号 | 目录 |
1 | HTML满屏跳动的爱心(可写字) |
2 | HTML五彩缤纷的爱心 |
3 | HTML满屏漂浮爱心 |
4 | HTML情人节快乐 |
5 | HTML蓝色爱心射线 |
6 | HTML跳动的爱心(简易版) |
7 | HTML粒子爱心 |
8 | HTML蓝色动态爱心 |
9 | HTML跳动的爱心(双心版) |
10 | HTML橙色动态粒子爱心 |
11 | HTML旋转爱心 |
12 | HTML爱情树 |
13 | HTML3D相册 |
14 | HTML旋转相册 |
15 | HTML基础烟花秀 |
16 | HTML炫酷烟花秀 |
17 | HTML粉色烟花秀 |
18 | HTML新春烟花 |
19 | HTML龙年大吉 |
20 | HTML圣诞树 |
21 | HTML大雪纷飞 |
22 | HTML想见你 |
23 | HTML元素周期表 |
24 | HTML飞舞的花瓣 |
25 | HTML星空特效 |
26 | HTML黑客帝国字母雨 |
27 | HTML哆啦A梦 |
28 | HTML流星雨 |
29 | HTML沙漏爱心 |
30 | HTML爱心字母雨 |
31 | HTML爱心流星雨 |
32 | HTML生日蛋糕 |
33 | HTML3D旋转相册 |
34 | HTML流光爱心 |
35 | HTML满屏飘字 |
36 | HTML飞舞爱心 |
写在前面
HTML语言实现飞舞的爱心完整代码。
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>飞舞爱心</title>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
overflow: hidden;
}
body {
position: relative;
background: #000;
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<!-- partial -->
<script>
class Tool {
// random number.
static randomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
// random color rgb.
static randomColorRGB() {
return (
"rgb(" +
this.randomNumber(0, 255) +
", " +
this.randomNumber(0, 255) +
", " +
this.randomNumber(0, 255) +
")"
);
}
// random color hsl.
static randomColorHSL(hue, saturation, lightness) {
return (
"hsl(" +
hue +
", " +
saturation +
"%, " +
lightness +
"%)"
);
}
// gradient color.
static gradientColor(ctx, cr, cg, cb, ca, x, y, r) {
const col = cr + "," + cg + "," + cb;
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
g.addColorStop(0, "rgba(" + col + ", " + (ca * 1) + ")");
g.addColorStop(0.5, "rgba(" + col + ", " + (ca * 0.5) + ")");
g.addColorStop(1, "rgba(" + col + ", " + (ca * 0) + ")");
return g;
}
}
/*
When want to use Angle and radian.
*/
class Angle {
constructor(a) {
this.a = a;
this.rad = (this.a * Math.PI) / 180;
}
incDec(num) {
this.a += num;
this.rad = (this.a * Math.PI) / 180;
}
}
/*
variable for canvas.
*/
let canvas;
let offCanvas;
class Canvas {
constructor(bool) {
// create canvas.
this.canvas = document.createElement("canvas");
// if on screen.
if (bool === true) {
this.canvas.style.position = 'relative';
this.canvas.style.display = 'block';
this.canvas.style.top = 0;
this.canvas.style.left = 0;
document.getElementsByTagName("body")[0].appendChild(this.canvas);
}
this.ctx = this.canvas.getContext("2d");
this.width = this.canvas.width = window.innerWidth;
this.height = this.canvas.height = window.innerHeight;
// size.
this.width < 768 ? this.heartSize = 180 : this.heartSize = 250;
// mouse infomation.
this.mouseX = null;
this.mouseY = null;
// sprite array and quantity.
this.hearts = [];
this.offHeartNum = 1;
this.offHearts = [];
// offscreen data.
this.data = null;
}
onInit() {
let index = 0;
for (let i = 0; i < this.height; i += 12) {
for (let j = 0; j < this.width; j += 12) {
let oI = (j + i * this.width) * 4 + 3;
if (this.data[oI] > 0) {
index++;
const h = new Heart(canvas.ctx, j + Tool.randomNumber(-3, 3), i + Tool.randomNumber(-3, 3), Tool.randomNumber(6, 12), index);
canvas.hearts.push(h);
}
}
}
}
offInit() {
for (let i = 0; i < this.offHeartNum; i++) {
const s = new Heart(this.ctx, this.width / 2, this.height / 2.3, this.heartSize);
this.offHearts.push(s);
}
for (let i = 0; i < this.offHearts.length; i++) {
this.offHearts[i].offRender(i);
}
// data
this.data = this.ctx.getImageData(0, 0, this.width, this.height).data;
// on screen init.
this.onInit();
}
render() {
this.ctx.clearRect(0, 0, this.width, this.height);
for (let i = 0; i < this.hearts.length; i++) {
this.hearts[i].render(i);
}
}
resize() {
this.offHearts = [];
this.hearts = [];
this.width = this.canvas.width = window.innerWidth;
this.height = this.canvas.height = window.innerHeight;
this.width < 768 ? this.heartSize = 180 : this.heartSize = 250;
}
}
class Heart {
constructor(ctx, x, y, r, i) {
this.ctx = ctx;
this.init(x, y, r, i);
}
init(x, y, r, i) {
this.x = x;
this.xi = x;
this.y = y;
this.yi = y;
this.r = r;
this.i = i * 0.5 + 200;
this.l = this.i;
this.c = `hsl(330, ${Tool.randomNumber(90, 100)}%, ${Tool.randomNumber(65, 75)}%)`;
this.a = new Angle(Tool.randomNumber(0, 360));
this.v = {
x: Math.random(),
y: -Math.random()
};
this.ga = Math.random();
}
draw() {
const ctx = this.ctx;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.globalAlpha = this.ga;
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.moveTo(this.x, this.y + this.r);
ctx.bezierCurveTo(
this.x - this.r - this.r / 5,
this.y + this.r / 1.5,
this.x - this.r,
this.y - this.r,
this.x,
this.y - this.r / 5
);
ctx.bezierCurveTo(
this.x + this.r,
this.y - this.r,
this.x + this.r + this.r / 5,
this.y + this.r / 1.5,
this.x,
this.y + this.r
);
ctx.closePath();
ctx.fill();
ctx.restore();
}
updateParams() {
this.a.incDec(1);
Math.sin(this.a.rad) < 0 ? this.r = -Math.sin(this.a.rad) * 20 : this.r = Math.sin(this.a.rad) * 20;
}
updatePosition() {
this.l -= 1;
if (this.l < 0) {
this.v.y -= 0.01;
this.v.x += 0.02;
this.y += this.v.y;
this.x += this.v.x;
}
}
wrapPosition() {
if (this.x > canvas.width * 1.5) {
this.init(this.xi, this.yi, Tool.randomNumber(6, 12), this.i);
}
}
render() {
this.wrapPosition();
this.updateParams();
this.updatePosition();
this.draw();
}
offRender(i) {
this.draw();
}
}
(function () {
"use strict";
window.addEventListener("load", function () {
offCanvas = new Canvas(false);
canvas = new Canvas(true);
offCanvas.offInit();
function render() {
window.requestAnimationFrame(function () {
canvas.render();
render();
});
}
render();
// event
window.addEventListener("resize", function () {
canvas.resize();
offCanvas.resize();
offCanvas.offInit();
}, false);
});
})();
</script>
</body>
</html>
代码分析
这段代码通过 HTML、CSS 和 JavaScript 实现了一个飞舞爱心的动画效果。以下将从代码的结构、功能、逻辑和技术实现等多个方面进行详细分析。
一、代码结构和总体概述
-
HTML 部分
-
定义了基础的 HTML 结构,设置了
<!DOCTYPE html>
声明,语言为en
,并通过<head>
部分导入 CSS 样式。 -
<body>
标签内主要包含 JavaScript 脚本,未添加其他内容。这表明所有的视觉元素均通过 Canvas 动态绘制,无静态 HTML 内容。
-
-
CSS 部分
-
全局样式重置:通过
*
选择器清除了所有元素的默认边距和内边距。 -
HTML 和 body 的
overflow
设置为hidden
,使页面无法滚动,确保动画完整显示。 -
背景颜色设置为黑色,强调彩色爱心的视觉效果。
-
-
JavaScript 部分
-
主要逻辑由多个类和立即执行函数
(function(){...})()
构成。 -
Tool
类提供了一些工具方法,包括随机数生成和颜色生成等。 -
Angle
类用于处理角度和弧度转换。 -
Canvas
类负责管理画布的初始化、元素渲染和窗口缩放适配。 -
Heart
类定义了爱心的属性、绘制方法和动态行为。
-
二、功能分析
-
随机颜色生成
-
Tool
类定义了randomColorRGB
和randomColorHSL
方法,用于生成随机 RGB 和 HSL 颜色。gradientColor
方法进一步提供了径向渐变色的生成。
-
-
角度管理
-
Angle
类封装了角度与弧度的关系,并提供了角度递增和递减的功能。这在爱心的动态变化中起到了关键作用。
-
-
画布初始化
-
Canvas
类用于创建画布并根据屏幕大小动态调整尺寸。通过this.width
和this.height
确定画布的宽高,同时记录鼠标位置。
-
-
爱心绘制
-
Heart
类实现了爱心的绘制逻辑,基于贝塞尔曲线绘制对称的心形图案。 -
每个爱心的颜色、透明度和大小都由随机数控制,呈现多样化的视觉效果。
-
-
动态行为
-
爱心会在屏幕中飞舞,逐渐远离原始位置。
-
通过调整
this.v
(速度)和this.a
(角度)实现运动轨迹的动态变化。
-
-
窗口适配
-
当窗口大小改变时,重新初始化画布和爱心,确保动画效果适配新尺寸。
-
三、核心技术实现
-
Canvas 元素的动态创建
-
JavaScript 通过
document.createElement("canvas")
动态创建画布,并将其添加到页面中。 -
通过
getContext("2d")
获取画布上下文,执行绘图操作。
-
-
贝塞尔曲线绘制心形
-
Heart
类中使用bezierCurveTo
方法绘制了左右对称的心形曲线。 -
具体实现中,两个贝塞尔曲线控制点的位置决定了心形的对称性和弧度。
-
-
颜色渐变和透明度变化
-
爱心的颜色使用 HSL 色值,动态生成亮度和饱和度。通过全局透明度
globalAlpha
实现爱心的透明效果。
-
-
动画实现
-
动画基于
window.requestAnimationFrame
方法实现,该方法比setInterval
更高效,适配屏幕刷新率。 -
动画帧中调用
canvas.render()
方法,逐帧绘制爱心的位置、大小和透明度变化。
-
-
多画布联动
-
使用了两个画布:一个离屏画布(
offCanvas
)用于生成基础数据;另一个在屏幕上展示爱心动画。
-
四、详细逻辑分析
-
工具类(Tool)
- 提供了生成随机数和颜色的工具方法:
-
randomNumber(min, max)
:生成min
到max
之间的随机整数。 -
randomColorRGB
和randomColorHSL
:分别生成 RGB 和 HSL 随机颜色,用于动态变化。
-
- 提供了生成随机数和颜色的工具方法:
-
画布类(Canvas)
- 初始化
-
创建画布,设置宽高。
-
根据屏幕宽度调整爱心尺寸,确保在不同设备上都有合适的显示比例。
-
- 离屏画布(
offCanvas
)-
离屏画布用于生成爱心的初始位置数据,避免直接在主画布上操作,提高性能。
-
- 渲染
-
主循环调用
render
方法绘制每一帧。 -
通过遍历
this.hearts
数组,逐个绘制爱心。
-
- 初始化
-
爱心类(Heart)
- 绘制逻辑
-
使用贝塞尔曲线描绘心形,基于动态参数更新形状和大小。
-
设置全局透明度和颜色,增加视觉层次感。
-
- 运动逻辑
-
爱心的初始位置为随机生成,运动方向和速度通过
v.x
和v.y
控制。 -
超出屏幕后重新初始化位置和属性,实现循环效果。
-
- 绘制逻辑
-
动态交互
-
页面监听
resize
事件,当窗口大小改变时,重新初始化画布和离屏画布数据,确保动画效果保持一致。
-
五、总结
这段代码通过 JavaScript 精心设计了一个动态飞舞的爱心效果,充分展示了 Canvas 的强大能力。整体结构清晰,功能丰富,逻辑合理,是一个兼具美观与性能的动画实现方案。这种实现方式不仅可以用于网页装饰,还可以扩展为互动游戏或者其他创意场景的基础模块。
写在后面
我是一只有趣的兔子,感谢你的喜欢!