HTML飞舞的爱心

目录

系列文章

写在前面

完整代码

代码分析

写在后面


系列文章

|----|------------------------------------------------------------------------------------------------------------|
| 序号 | 目录 |
| 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语言实现飞舞的爱心完整代码。

完整代码

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 实现了一个飞舞爱心的动画效果。以下将从代码的结构、功能、逻辑和技术实现等多个方面进行详细分析。


一、代码结构和总体概述

  1. HTML 部分

    • 定义了基础的 HTML 结构,设置了 <!DOCTYPE html> 声明,语言为 en,并通过 <head> 部分导入 CSS 样式。

    • <body> 标签内主要包含 JavaScript 脚本,未添加其他内容。这表明所有的视觉元素均通过 Canvas 动态绘制,无静态 HTML 内容。

  2. CSS 部分

    • 全局样式重置:通过 * 选择器清除了所有元素的默认边距和内边距。

    • HTML 和 body 的 overflow 设置为 hidden,使页面无法滚动,确保动画完整显示。

    • 背景颜色设置为黑色,强调彩色爱心的视觉效果。

  3. JavaScript 部分

    • 主要逻辑由多个类和立即执行函数 (function(){...})() 构成。

    • Tool 类提供了一些工具方法,包括随机数生成和颜色生成等。

    • Angle 类用于处理角度和弧度转换。

    • Canvas 类负责管理画布的初始化、元素渲染和窗口缩放适配。

    • Heart 类定义了爱心的属性、绘制方法和动态行为。


二、功能分析

  1. 随机颜色生成

    • Tool 类定义了 randomColorRGBrandomColorHSL 方法,用于生成随机 RGB 和 HSL 颜色。gradientColor 方法进一步提供了径向渐变色的生成。
  2. 角度管理

    • Angle 类封装了角度与弧度的关系,并提供了角度递增和递减的功能。这在爱心的动态变化中起到了关键作用。
  3. 画布初始化

    • Canvas 类用于创建画布并根据屏幕大小动态调整尺寸。通过 this.widththis.height 确定画布的宽高,同时记录鼠标位置。
  4. 爱心绘制

    • Heart 类实现了爱心的绘制逻辑,基于贝塞尔曲线绘制对称的心形图案。

    • 每个爱心的颜色、透明度和大小都由随机数控制,呈现多样化的视觉效果。

  5. 动态行为

    • 爱心会在屏幕中飞舞,逐渐远离原始位置。

    • 通过调整 this.v(速度)和 this.a(角度)实现运动轨迹的动态变化。

  6. 窗口适配

    • 当窗口大小改变时,重新初始化画布和爱心,确保动画效果适配新尺寸。

三、核心技术实现

  1. Canvas 元素的动态创建

    • JavaScript 通过 document.createElement("canvas") 动态创建画布,并将其添加到页面中。

    • 通过 getContext("2d") 获取画布上下文,执行绘图操作。

  2. 贝塞尔曲线绘制心形

    • Heart 类中使用 bezierCurveTo 方法绘制了左右对称的心形曲线。

    • 具体实现中,两个贝塞尔曲线控制点的位置决定了心形的对称性和弧度。

  3. 颜色渐变和透明度变化

    • 爱心的颜色使用 HSL 色值,动态生成亮度和饱和度。通过全局透明度 globalAlpha 实现爱心的透明效果。
  4. 动画实现

    • 动画基于 window.requestAnimationFrame 方法实现,该方法比 setInterval 更高效,适配屏幕刷新率。

    • 动画帧中调用 canvas.render() 方法,逐帧绘制爱心的位置、大小和透明度变化。

  5. 多画布联动

    • 使用了两个画布:一个离屏画布(offCanvas)用于生成基础数据;另一个在屏幕上展示爱心动画。

四、详细逻辑分析

  1. 工具类(Tool)

    • 提供了生成随机数和颜色的工具方法:
      • randomNumber(min, max):生成 minmax 之间的随机整数。

      • randomColorRGBrandomColorHSL:分别生成 RGB 和 HSL 随机颜色,用于动态变化。

  2. 画布类(Canvas)

    • 初始化

      • 创建画布,设置宽高。

      • 根据屏幕宽度调整爱心尺寸,确保在不同设备上都有合适的显示比例。

    • 离屏画布(offCanvas

      • 离屏画布用于生成爱心的初始位置数据,避免直接在主画布上操作,提高性能。
    • 渲染

      • 主循环调用 render 方法绘制每一帧。

      • 通过遍历 this.hearts 数组,逐个绘制爱心。

  3. 爱心类(Heart)

    • 绘制逻辑

      • 使用贝塞尔曲线描绘心形,基于动态参数更新形状和大小。

      • 设置全局透明度和颜色,增加视觉层次感。

    • 运动逻辑

      • 爱心的初始位置为随机生成,运动方向和速度通过 v.xv.y 控制。

      • 超出屏幕后重新初始化位置和属性,实现循环效果。

  4. 动态交互

    • 页面监听 resize 事件,当窗口大小改变时,重新初始化画布和离屏画布数据,确保动画效果保持一致。

五、总结

这段代码通过 JavaScript 精心设计了一个动态飞舞的爱心效果,充分展示了 Canvas 的强大能力。整体结构清晰,功能丰富,逻辑合理,是一个兼具美观与性能的动画实现方案。这种实现方式不仅可以用于网页装饰,还可以扩展为互动游戏或者其他创意场景的基础模块。

写在后面

我是一只有趣的兔子,感谢你的喜欢!

相关推荐
珹洺几秒前
从 HTML 到 CSS:开启网页样式之旅(开篇之一)——CSS 初体验与网页样式新征程
前端·javascript·css·前端框架·html
wh柒八九5 分钟前
wkhtmltopdf的安装与使用
pdf·html
前端Hardy8 分钟前
HTML&CSS:翻书加载效果
前端·javascript·css·3d·html·css3
问道飞鱼10 分钟前
【前端知识】SCSS(Sassy CSS)是一种CSS预处理器语言
前端·css·less·scss
snow@li27 分钟前
vue3 + ts:开发插件 / Plugins / 注册全局实例 / 在 template 与 setup 中使用 / provide、inject
前端·javascript·vue.js
命运之光43 分钟前
【经典】高级动态抽奖系统(HTML,CSS、JS)
前端·css·html
დ旧言~1 小时前
实战项目 Boost 搜索引擎
服务器·c语言·前端·网络·汇编·c++
森屿Serien1 小时前
Javaweb关于web.xml的相关配置信息
xml·前端
夏天想2 小时前
前端安全和解决方案
前端·安全
生椰拿铁You2 小时前
11 —— 打包模式的应用
前端