目录
系列文章
写在前面
完整代码
代码分析
写在后面
系列文章
序号 | 目录 |
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);}// datathis.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();// eventwindow.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 的强大能力。整体结构清晰,功能丰富,逻辑合理,是一个兼具美观与性能的动画实现方案。这种实现方式不仅可以用于网页装饰,还可以扩展为互动游戏或者其他创意场景的基础模块。
写在后面
我是一只有趣的兔子,感谢你的喜欢!