Advertisement

【HTML】过年不能放烟花,那就放电子烟花

阅读量:

鑫宝Code

🌈个人主页:鑫宝Code
🔥热门专栏:闲话杂谈炫酷HTML | JavaScript基础 **
💫个人格言: "如无必要,勿增实体"**


文章目录

    • 闲谈

    • 开干

      • 初始化粒子
      • 粒子聚合成烟花
      • 让粒子动起来
    • 全部代码

闲谈

普通百姓过节嘛……总要放点什么吧。不过也不全是哦……实在没有办法嘛……毕竟城市里人太多……反正咱们懂行嘛……就想着借助现代科技来过个好年……既省心又省力。

开干

第一步,请明确整个思路;我认为可将其分解为两个关键步骤:第一部分涉及Canvas元素的动态更新;第二部分则需结合requestAnimationFrame机制完成动画渲染;此外还需运用基础的物理运动学原理来计算物体轨迹参数;最后别忘了加入一些辅助说明文字以提高可读性

首先随后生成一个烟花爆炸产生的粒子,这需要综合运用Canvas技术和基础物理知识。
最终利用动画技术将各个粒子的运动轨迹依次连接起来。

大家看下,我感觉应该没什么问题了,于是深入细节分析。

初始化粒子

我们先分析一下这个粒子有哪些属性,我罗列如下

  • 粒子的初始位置坐标(x,y)
    • 粒子的速度分量(Vx,Vy)
    • 粒子的颜色为Color
    • 粒子的大小参数Radius
    • 粒子的透明度随粒子下落而逐渐减弱(opacity)

属性分析已经完成之后,我们将深入探讨一个关键问题:在爆炸瞬间,粒子是如何运动的?由此可见,这些粒子均具有初始速度,其运动轨迹遵循自由落体模式。

image.png

我们将速度分成水平方向Vx以及竖直方向Vy,于是我们可以得到

Vx在整个运动过程中保持恒定。
Vy的速度遵循公式:Vy = Vy - gt;这意味着每秒钟会减少g个单位的速度;由于g是一个固定值,在模拟过程中我们采用每隔10ms(即屏幕刷新周期)减少0.15px的速度变化。

该变量Vy在代码中表示纵向运动的速度,在实现物理动画时需要特别注意其变化方向与屏幕坐标系的关系。具体来说,在计算机图形界面中,默认的屏幕坐标系以右上角为原点(即x=0,y=0的位置),因此向下运动的方向与数学坐标系中的正y轴方向一致。因此,在本例中使用加号来表示纵向速度的变化量(如Vy += 0.15)是为了确保向下运动的速度为正数(即往上走速度为负数),从而符合直观的方向性定义。

于是我们便可撰写如下代码

复制代码
    class Dot {
      constructor(x, y, color, Vx, Vy) {
    this.x = x;
    this.y = y;
    this.Vx = Vx;
    this.Vy = Vy;
    this.color = color;
    this.radius = 2.5;
    this.opacity = 1;
      }
    
      update() {
    // 每一帧x和y轴移动的距离
    this.x += this.Vx;
    this.y += this.Vy;
    // 每一帧速度变化
    this.Vy += 0.15;
    // 每一帧清晰度较小
    this.opacity = this.opacity - 0.01;
      }
    
      draw() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = this.color;
    ctx.globalAlpha = this.opacity;
    ctx.fill();
      }
    }

粒子聚合成烟花

怎样才能将粒子聚合成烟花?我们可以创建一个烟花类就足够了。对于任何一个烟花而言,在确定爆炸的位置上是关键的——这主要涉及x轴和y轴的坐标值。

  • 粒子的色调如何? 为了提升粒子色调的饱和度以获得更丰富的视觉效果,在视觉效果设计中我们会采用基于HSL色彩模型的方案。值得注意的是,在色彩表示方法中与RGB与HEX编码体系不同的是HSL模型通过色相、饱和度与明度三个参数来精确控制色调。具体实现时使用的代码逻辑如下:
复制代码
    // 是JavaScript中动态生成一个随机色相、饱和度为100%、亮度为50%的HSL颜色值。
    const color = `hsl(${Math.random() * 360}, 100%, 50%)`;

对于粒子的初始速度设定?我们可以设定Vx速度的范围在[-x,x]区间内,并支持粒子在横向轴上向左或向右运动。而对于Vy速度,则全部采用向上的方向,并且由于屏幕坐标系中右上角为(0,0)点的原因,默认向上运动的速度被定义为负值。

复制代码
    const Vx = (Math.random() - 0.5) * 6;
    const Vy = -Math.random() * 10;

于是我们可以将烟花类写好

复制代码
    class Firework {
      constructor(x, y) {
    this.x = x;
    this.y = y;
    this.dots = [];
    // 一次性放40个烟花粒子
    for (let i = 0; i < 40; i++) {
      const color = `hsl(${Math.random() * 360}, 100%, 50%)`;
      const Vx = (Math.random() - 0.5) * 6;
      const Vy = -Math.random() * 10;
      this.dots.push(new Dot(x, y, color, Vx, Vy));
    }
      }
    
      draw() {
    this.dots.forEach((particle) => particle.update());
    this.dots.forEach((particle) => particle.draw());
      }
    }

让粒子动起来

我们拥有一批非常有趣的粒子了。如何让这些粒子开始移动呢?当然调用请求AnimationFrame方法,并参考文档中的相关内容。

为了实现动画效果并确保在页面刷新前更新画面,请使用该方法并传递所需的回调函数。

直接调用Fireworkdraw于是我们就能得到这样的效果。

Feb-16-2024 14-02-32.gif

但是粒子已经有了,但我们要的是粒子像流星那样有一个尾巴.该怎么设置呢?

复制代码
    ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

之后,我们可以通过点击屏幕,触发这个烟花绽放。相应的代码如下

复制代码
    // 鼠标点击触发烟花效果
    canvas.addEventListener("click", (event) => {
      const x = event.clientX;
      const y = event.clientY;
      // 所有的烟花
      fireworks.push(new Firework(x, y));
    });
    
    function animate() {
      ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      fireworks.forEach((firework, index) => {
    // 去除透明度为0的烟花
    if (firework.dots[0].opacity <= 0) {
      fireworks.splice(index, 1);
    } else {
      firework.draw();
    }
      });
      requestAnimationFrame(animate);
    }

对应的效果图如下:

Feb-16-2024 14-11-13.gif

全部代码

复制代码
    <!DOCTYPE html>
    <html lang="en">
      <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>烟花效果</title>
    <style>
      body {
        margin: 0;
      }
    </style>
      </head>
      <body>
    <canvas id="fireworksCanvas"></canvas>
    <script>
      const canvas = document.getElementById("fireworksCanvas");
      const ctx = canvas.getContext("2d");
      // 所有烟花的集合
      let fireworks = [];
    
      // 设置画布大小
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    
      // 监听窗口大小变化
      window.addEventListener("resize", () => {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
      });
    
      class Dot {
        constructor(x, y, color, Vx, Vy) {
          this.x = x;
          this.y = y;
          this.Vx = Vx;
          this.Vy = Vy;
          this.color = color;
          this.radius = 2.5;
          this.opacity = 1;
        }
        update() {
          this.x += this.Vx;
          this.y += this.Vy;
          this.Vy += 0.15;
          this.opacity = this.opacity - 0.01;
        }
        draw() {
          ctx.beginPath();
          ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
          ctx.fillStyle = this.color;
          ctx.globalAlpha = this.opacity;
          ctx.fill();
        }
      }
    
      class Firework {
        constructor(x, y) {
          this.x = x;
          this.y = y;
          this.dots = [];
          for (let i = 0; i < 40; i++) {
            const color = `hsl(${Math.random() * 360}, 100%, 50%)`;
            const Vx = (Math.random() - 0.5) * 6;
            const Vy = -Math.random() * 10;
            this.dots.push(new Dot(x, y, color, Vx, Vy));
          }
        }
        draw() {
          this.dots.forEach((particle) => particle.update());
          this.dots.forEach((particle) => particle.draw());
        }
      }
    
      // 鼠标点击触发烟花效果
      canvas.addEventListener("click", (event) => {
        const x = event.clientX;
        const y = event.clientY;
        fireworks.push(new Firework(x, y));
      });
    
      function animate() {
        ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        fireworks.forEach((firework, index) => {
          if (firework.dots[0].opacity <= 0) {
            fireworks.splice(index, 1);
          } else {
            firework.draw();
          }
        });
        requestAnimationFrame(animate);
      }
    
      // 启动动画
      animate();
    </script>
      </body>
    </html>

全部评论 (0)

还没有任何评论哟~