首页 > 其他分享 >HTML飞舞的爱心

HTML飞舞的爱心

时间:2024-11-25 22:34:42浏览次数:5  
标签:canvas 飞舞 ctx 画布 width 爱心 HTML randomNumber

目录

系列文章

写在前面

完整代码

代码分析

写在后面


系列文章

序号目录
1HTML满屏跳动的爱心(可写字)
2HTML五彩缤纷的爱心
3HTML满屏漂浮爱心
4HTML情人节快乐
5HTML蓝色爱心射线
6HTML跳动的爱心(简易版)
7HTML粒子爱心
8HTML蓝色动态爱心
9HTML跳动的爱心(双心版)
10HTML橙色动态粒子爱心
11HTML旋转爱心
12HTML爱情树
13HTML3D相册
14HTML旋转相册
15HTML基础烟花秀
16HTML炫酷烟花秀
17HTML粉色烟花秀
18HTML新春烟花
19HTML龙年大吉
20HTML圣诞树
21HTML大雪纷飞
22HTML想见你
23HTML元素周期表
24HTML飞舞的花瓣
25HTML星空特效
26HTML黑客帝国字母雨
27HTML哆啦A梦
28HTML流星雨
29HTML沙漏爱心
30HTML爱心字母雨
31HTML爱心流星雨
32HTML生日蛋糕
33HTML3D旋转相册
34HTML流光爱心
35HTML满屏飘字
36HTML飞舞爱心

写在前面

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 类定义了 randomColorRGB 和 randomColorHSL 方法,用于生成随机 RGB 和 HSL 颜色。gradientColor 方法进一步提供了径向渐变色的生成。

  2. 角度管理

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

  3. 画布初始化

    • Canvas 类用于创建画布并根据屏幕大小动态调整尺寸。通过 this.width 和 this.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):生成 min 到 max 之间的随机整数。

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

  2. 画布类(Canvas)

    • 初始化
      • 创建画布,设置宽高。

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

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

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

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

  3. 爱心类(Heart)

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

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

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

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

  4. 动态交互

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


五、总结

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

写在后面

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

标签:canvas,飞舞,ctx,画布,width,爱心,HTML,randomNumber
From: https://blog.csdn.net/m0_68111267/article/details/144040843

相关文章

  • HTML-CSS-JS-day01:html常见的标签
    前端--一、相关概念1、前后端--技术层面程序运行在客户端上,这样的程序称为前端程序运行在服务器端2、前后台--应用层面如果服务是面向所有用户,这样的服务称为前台服务如果服务是面向相关的管理或者维护人员,这样的服务称为后台服务二、web(网页)访问流程基于http/htt......
  • FastHtml:Websockets
    FastHTML支持WebSockets,允许你与客户端建立持久连接并进行双向通信。以下是如何使用WebSockets进行双向通信的示例:fromfasthtml.commonimport*fromasyncioimportsleepapp,rt=fast_app(exts='ws')#启用WebSocket支持defmk_inp():returnInput(id='m......
  • FastHtml :服务器发送事件SSE
    FastHTML支持服务器发送事件(SSE),允许服务器向客户端发送实时数据更新。以下是如何使用SSE实现实时数据更新的示例:fromfasthtml.commonimport*fromasyncioimportsleepapp,rt=fast_app()app.exts='sse'#启用SSE支持@rt("/")defindex():retu......
  • HTML5规范将元素分为哪几个大类?分别说说它们的特点
    HTML5规范将元素大致分为以下几大类,基于它们在文档中的角色和允许的内容:1.元数据内容(Metadatacontent):这些元素提供关于HTML文档的信息,通常位于<head>元素内。它们不影响文档的显示,而是用于浏览器、搜索引擎和其他工具。特点:不在页面中呈现视觉内容,主要用于描述文档、......
  • 从0学网安之HTML(超文本标记语言)基础
    声明!学习视频来自B站up主**泷羽sec**有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关,切勿触碰法律底线,否则后果自负!!!!有兴趣的小伙伴可以点击下面连接进入b站主页[B站......
  • 怎样去除html标签之间换行产生的空格?
    HTML标签之间换行产生的空格主要由两种情况导致,因此也有不同的解决方法:标签之间的普通空格/换行:浏览器会将HTML代码中的多个空格、换行符渲染成一个空格。解决方法:移除空格和换行:最直接的方法是删除标签之间的空格和换行符,将标签紧挨着写。但这会降低代码的可读......
  • document.write和innerHTML有什么区别?
    document.write和innerHTML都是用于操作网页内容的JavaScript方法,但它们的工作方式和适用场景有很大的区别:document.write():写入时机:document.write()会直接写入到文档流中。如果在页面加载完成后调用,它会先清空整个文档,然后再写入内容。这通常会导致页面闪烁并重......
  • 使用正则去掉html中标签与标签之间的空格
    要使用正则表达式去除HTML标签之间多余的空格,需要考虑几种情况:多个空格压缩成一个空格:这可以使用\s+匹配一个或多个空格字符,并将其替换为单个空格。标签之间的换行符:换行符也应该被考虑在内,可以使用\s+来匹配,因为它包含了换行符。避免移除<pre>标签内的空......
  • 有使用过HTML5的跟踪元素吗?说说你对它的理解
    是的,我了解HTML5的<track>元素。它主要用于为媒体元素(例如<video>和<audio>)添加文本轨道,例如字幕、标题、章节、元数据等。<track>元素本身并不显示任何内容,它只是为浏览器提供了一种加载外部轨道文件的方式。以下是<track>元素及其用法的详细解释:主要用途:字幕(Sub......
  • 举例说明HTML5的Canvas元素有什么用途?
    HTML5的Canvas元素就像一块画布,允许开发者使用JavaScript在网页上动态地绘制图形、图表、图像以及动画。它提供了一个基于像素的绘图区域,可以用来创建各种视觉效果。以下是一些Canvas元素的常见用途示例:1.绘制图形和图表:简单的形状:可以绘制矩形、圆形、线条、弧线......