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 做個好玩的網站背景


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM