【CSS Tricks】如何做一个粒子效果的logo

效果展示

代码展示

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>粒子效果Logo</title>
    <style>
      body,
      html {
        margin: 0;
        padding: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <script>
      class Particle {
        constructor(x, y) {
          this.x = Math.random() * canvas.width;
          this.y = Math.random() * canvas.height;
          this.dest = { x, y };
          this.r = Math.random() * 1 * Math.PI;
          this.vx = (Math.random() - 0.5) * 25;
          this.vy = (Math.random() - 0.5) * 25;
          this.accX = 0;
          this.accY = 0;
          this.friction = Math.random() * 0.025 + 0.94;
          this.color = colors[Math.floor(Math.random() * colors.length)];
        }

        render() {
          this.accX = (this.dest.x - this.x) / 1000;
          this.accY = (this.dest.y - this.y) / 1000;
          this.vx += this.accX;
          this.vy += this.accY;
          this.vx *= this.friction;
          this.vy *= this.friction;
          this.x += this.vx;
          this.y += this.vy;

          ctx.fillStyle = this.color;
          ctx.beginPath();
          ctx.arc(this.x, this.y, this.r, Math.PI * 2, false);
          ctx.fill();

          const a = this.x - mouse.x;
          const b = this.y - mouse.y;
          const distance = Math.sqrt(a * a + b * b);
          if (distance < radius * 75) {
            this.accX = (this.x - mouse.x) / 50;
            this.accY = (this.y - mouse.y) / 50;
            this.vx += this.accX;
            this.vy += this.accY;
          }
        }
      }

      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      let particles = [];
      let mouse = { x: -9999, y: -9999 };
      const colors = ["#3f73fa", "#7ffde1", "#aedce9"];
      let radius = 1.5;

      function onMouseMove(e) {
        mouse.x = e.clientX;
        mouse.y = e.clientY;
      }

      function onTouchMove(e) {
        if (e.touches.length > 0) {
          mouse.x = e.touches[0].clientX;
          mouse.y = e.touches[0].clientY;
        }
      }

      function onTouchEnd(e) {
        mouse.x = -9999;
        mouse.y = -9999;
      }

      function initScene() {
        particles = [];
        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        for (let x = 0; x < imgData.width; x += 6) {
          for (let y = 0; y < imgData.height; y += 6) {
            const i = (y * imgData.width + x) * 4;
            if (imgData.data[i + 3] > 200) {
              particles.push(new Particle(x, y));
            }
          }
        }
      }

      function render() {
        requestAnimationFrame(render);
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        particles.forEach((particle) => particle.render());
      }

      window.addEventListener("resize", () => {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        initScene();
      });
      window.addEventListener("mousemove", onMouseMove);
      window.addEventListener("touchmove", onTouchMove);
      window.addEventListener("touchend", onTouchEnd);

      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      const img = new Image();
      img.onload = () => {
        ctx.drawImage(
          img,
          canvas.width / 2 - img.width / 2,
          canvas.height / 2 - img.height / 2
        );
        initScene();
        render();
      };
      img.src = "./qbbmnn.png";
    </script>
  </body>
</html>

代码注解

代码的主要部分包括粒子类的定义、初始化过程、事件监听和动画循环。

粒子类(Particle)

每个粒子对象都有以下属性:

  • xy:粒子的当前位置。
  • dest.xdest.y:粒子的目标位置。
  • r:粒子随机的大小。
  • vxvy:粒子的水平和垂直速度。
  • accXaccY:粒子的水平和垂直加速度。
  • friction:粒子的摩擦系数,影响其减速。
  • color:粒子的颜色。

render方法,用于更新粒子的位置并绘制它们。这个方法执行以下操作:

  • 计算粒子到目标位置的加速度。
  • 更新粒子的速度,考虑加速度和摩擦力。
  • 根据速度更新粒子的位置。
  • 绘制粒子。

初始化过程

初始化过程包括以下步骤:

  1. 设置画布的宽度和高度以匹配窗口的尺寸。
  2. 创建一个图像对象并设置src属性,以便加载图像。
  3. 当图像加载完成后,绘制到画布上。
  4. 调用initScene函数来创建粒子数组。
    initScene函数执行以下操作:
  5. 清空粒子数组。
  6. 获取画布上图像的数据。
  7. 遍历图像的每个像素,根据像素的透明度决定是否在该位置创建一个粒子。

事件监听

代码监听了以下事件:

  • resize:当窗口大小变化时,调整画布的大小并重新初始化场景。
  • mousemove:当鼠标移动时,更新mouse对象的xy属性。
  • touchmove:适配移动端,当触摸移动时,更新mouse对象的xy属性。
  • touchend:模拟手离开屏幕后,将mouse对象的xy属性重置为初始值。

动画循环

使用requestAnimationFrame根据屏幕刷新率去更新画面:

  1. 清空画布。
  2. 遍历粒子数组,调用每个粒子的render方法。

可以自定义的部分

  • radius:通过调整这个变量的值,控制鼠标弹开粒子的范围。
  • colors:根据自己喜好去填写多个颜色,最少两个。
  • img:可以准备一张透明底白色字的图片,粒子效果会吸附到白色字的笔触上。例如(因为是白色字透明底,所以需要在夜间模式下app才能看清):
相关推荐
GISer_Jing16 分钟前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪1 小时前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲5 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter7 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry7 小时前
Jetpack Compose 中的状态
前端
dae bal8 小时前
关于RSA和AES加密
前端·vue.js
柳杉8 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化