canvas实现粒子背景特效思路总结

canvas实现粒子背景特效思路总结

效果

GIF

源码

html

点击查看代码
复制<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>粒子背景</title>
    <style>
      canvas {
        margin: 50px auto;
        display: block;
        box-shadow: 0 0 10px rgb(0 0 0 / 50%);
      }
    </style>
  </head>
  <body>
    <canvas width="800" height="500"></canvas>

    <script src="./js/粒子背景.js"></script>
    <script>
      // 粒子集合
      let particles = []
      // 粒子数量
      const particleCount = 150
      // 鼠标位置对象
      let mouseOffset = null
      // 粒子画线的最小距离
      const minDistance = 100
      // 边界碰撞检测
      const collideDetect = true
      // 是否有鼠标交互
      const hasInteractMouse = true
      const canvas = document.querySelector('canvas')
      const ctx = canvas.getContext('2d')
      createParticles()
      draw()
      handleCanvasEvent()
      function draw() {
        // 清空画布
        canvas.width = canvas.width
        drawParticles(ctx)
        ctx.lineWidth = 0.1
        drawLines(ctx)

        requestAnimationFrame(draw)
      }

      // 创建粒子对象
      function createParticles() {
        for (let index = 0; index < particleCount; index++) {
          particles.push(
            new Particle(
              getRandomRange(0, canvas.width, canvas.height),
              getRandomRange(0, canvas.height)
            )
          )
        }
      }

      // 将粒子对象画到画布上
      function drawParticles(ctx) {
        ctx.beginPath()
        for (let index = 0; index < particles.length; index++) {
          const particle = particles[index]

          particle.draw(ctx)

          // 没有开启边界碰撞检测,越界的粒子,更新位置并保证其新位置不越界
          if (!collideDetect) {
            if (
              particle.x > canvas.width ||
              particle.x < 0 ||
              particle.y > canvas.height ||
              particle.y < 0
            ) {
              // 粒子直径
              const diameter = particle.radius * 2
              particle.updateCoordinate(
                getRandomRange(diameter, canvas.width - diameter),
                getRandomRange(diameter, canvas.height - diameter)
              )
            }
          } else {
            handleCollide(particle)
          }

          particle.x += particle.speedX
          particle.y += particle.speedY
        }
        ctx.fill()
      }

      // 绘制粒子连线
      function drawLines(ctx) {
        ctx.beginPath()
        let arr = [...particles]
        mouseOffset && (arr = [mouseOffset].concat(arr))
        for (let index = 0; index < particles.length; index++) {
          const particle = particles[index]

          for (let j = arr.length - 1; j >= 0; j--) {
            const particle2 = arr[j]
            if (particle === particle2) {
              continue
            }
            const distance = calDistance(
              particle.x,
              particle2.x,
              particle.y,
              particle2.y
            )
            if (distance < minDistance) {
              // 如果是鼠标,则让粒子向鼠标的位置移动,距离-10 保证粒子与鼠标之间的最小间距
              if (particle2 === mouseOffset && distance > minDistance - 10) {
                const xc = particle.x - particle2.x
                const yc = particle.y - particle2.y

                // 0.03 向鼠标坐标移动的速率
                particle.x -= xc * 0.03
                particle.y -= yc * 0.03
              }
              ctx.moveTo(particle.x, particle.y)
              ctx.lineTo(particle2.x, particle2.y)
            }
          }

          // 去掉重复比较
          arr.splice(arr.indexOf(particle), 1)
        }

        ctx.stroke()
      }

      // 粒子边界碰撞检测
      function handleCollide(particle) {
        if (
          (particle.speedX && particle.x + particle.radius > canvas.width) ||
          (particle.speedX < 0 && particle.x < particle.radius)
        ) {
          particle.speedX *= -1
        }
        if (
          (particle.speedY > 0 &&
            particle.y + particle.radius >= canvas.height) ||
          (particle.speedY < 0 && particle.y <= particle.radius)
        ) {
          particle.speedY *= -1
        }
      }

      // 处理canvas的事件
      function handleCanvasEvent() {
        if (hasInteractMouse) {
          canvas.onmousemove = function (e) {
            if (!mouseOffset) {
              mouseOffset = {}
            }
            mouseOffset.x = e.offsetX
            mouseOffset.y = e.offsetY
          }
        }
      }

      function calDistance(x1, x2, y1, y2) {
        return Math.hypot(x1 - x2, y1 - y2)
      }

      function getRandomRange(min, max) {
        return Math.random() * (max - min) + min
      }
    </script>
  </body>
</html>

粒子背景.js

点击查看代码
class Particle {
  constructor(x, y) {
    this.x = x
    this.y = y
    this.radius = 0.5
    this.speedX = this._getRandomRange(-1, 1)
    this.speedY = this._getRandomRange(-1, 1)
  }

  draw(ctx) {
    ctx.moveTo(this.x, this.y)
    ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
  }

  updateCoordinate(x, y) {
    this.x = x
    this.y = y
    return this
  }

  _getRandomRange(min, max) {
    return Math.random() * (max - min) + min
  }
}

主要思路

现象

  • 粒子之间、鼠标与粒子之间,小于一定的值会连线
  • 粒子的移动是随机的
  • 粒子向鼠标移动时,有一个加速的过程

思路梳理

  • 抽象圆形粒子类

    • 坐标,x,y。由外部传入
    • 移速,x,y。自己内部随机定义
    • 半径。内部定义
  • 准备粒子,画布上一直只有定量的粒子

    • 使用一个数组,承载创建的定量的粒子
  • 画布有鼠标移动事件的监听

    • 储存鼠标的位置,用来判断粒子与鼠标位置间是否应该连线
  • 批量绘制粒子

    • 绘制粒子时对粒子进行边界碰撞检测。或者到边界后更新粒子的位置(确保其不越界)
  • 批量绘制粒子间、鼠标和粒子间的连线

    • 如果粒子间的球心距离小于限定的连线距离,则在这两个粒子中间连线

    • 如果粒子和鼠标的距离小于限定的连线距离,则在鼠标和粒子中间连线

      • 连线前,如果粒子与鼠标位置间的距离小于一定间距,粒子向鼠标位置移动(有一个粒子围绕着鼠标的效果)

      • 移动的距离,有正、负。用鼠标的坐标和粒子的坐标做对应方向上的加减运算,再乘以一个值(表示移动速率,体现在粒子向鼠标移动的效果上)

      • 计算示例实现鼠标交互的重点

        • // 如果粒子本就在鼠标附近,不用再做移动(一定间距的限制,有一个粒子围绕鼠标但不挨着鼠标的效果)
          if (粒子与鼠标间的距离 < 连线限定距离 && 粒子与鼠标间的距离 > 一定间距) {
              // xDistance 正负不定
              const xDistance = 粒子坐标x - 鼠标坐标.x
              // yDistance 正负不定
              const yDistance = 粒子坐标y - 鼠标坐标.y
          
              const rate = 0.03
          
              // 无论xDistance、yDistance正负如何,这里都要保证是粒子在向鼠标移动
              粒子坐标.x -= xDistance * rate
              粒子坐标.y -= yDistance * rate
          }
          

参考

canvas粒子动画背景

用 canvas 做个好玩的网站背景

posted @   酉云良  阅读(961)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示