canvas绘制颜色流体效果

1,671 阅读1分钟

本文参考youtube上一位大佬的分享:www.youtube.com/watch?v=D6E…

实现效果:

思路:

  1. 多个的色块(这里用圆)随机运动,并且大小也随着运动变化
  2. 每个色块半径放大后,看起来就有了流体效果
  3. 更改色块之间的混合模式,实现不同颜色的混合效果

实现demo代码:

<!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>Document</title>
  </head>
  <style>
    * {
      outline: 0;
      margin: 0;
      padding: 0;
    }
    html {
      width: 100%;
      height: 100%;
    }
    body {
      width: 100%;
      height: 100%;
      overflow: hidden;

      background-color: #fff;
    }
    canvas {
      width: 100%;
      height: 100%;
    }
  </style>
  <body>
    <script>
      // 颜色集合
      const COLORS = [
        { r: 45, g: 74, b: 227 },
        { r: 250, g: 255, b: 89 },
        { r: 255, g: 104, b: 248 },
        { r: 44, g: 209, b: 252 },
        { r: 54, g: 233, b: 84 },
      ]
      class App {
        constructor() {
          // 创建画布
          this.canvas = document.createElement("canvas")
          document.body.appendChild(this.canvas)
          this.ctx = this.canvas.getContext("2d")

          this.pixelRatio = window.devicePixelRatio > 1 ? 2 : 1
          // 色块球的数量
          this.totalParticles = 15
          this.particles = []
          // 最大半径与最小半径,这个半径越大流体效果看起来更好看(个人喜好)
          this.maxRadius = 900
          this.minRadius = 400
          window.addEventListener("resize", this.resize.bind(this), false)
          this.resize()
          this.animate()
        }
        // 重置画布大小
        resize() {
          this.stageWidth = document.body.clientWidth
          this.stageHeight = document.body.clientHeight

          this.canvas.width = this.stageWidth * this.pixelRatio
          this.canvas.height = this.stageHeight * this.pixelRatio
          this.ctx.scale(this.pixelRatio, this.pixelRatio)

          // 色块之间的混合模式
          // lighten overlay saturation color luminosity 等,所有可选值请查看官方文档
          this.ctx.globalCompositeOperation = "saturation"
          this.createParticles()
        }

        // 创建色块球
        createParticles() {
          let curColor = 0
          this.particles = []
          for (let i = 0; i < this.totalParticles; i++) {
            this.particles.push(
              new GlowParticle(
                Math.random() * this.stageWidth,
                Math.random() * this.stageHeight,
                Math.random() * (this.maxRadius - this.minRadius) + this.minRadius,
                COLORS[curColor % COLORS.length]
              )
            )
            curColor++
          }
        }
        animate() {
          window.requestAnimationFrame(this.animate.bind(this))
          // 清除画布内容
          this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight)
          for (let i = 0; i < this.totalParticles; i++) {
            const item = this.particles[i]
            item.animate(this.ctx, this.stageWidth, this.stageHeight)
          }
        }
      }
      
      
      window.onload = () => {
        new App()
      }
      const PI2 = Math.PI * 2
      // 运动速度
      const speed = 2
      // 色块对象
      class GlowParticle {
        constructor(x, y, radius, rgb) {
          this.x = x
          this.y = y
          this.radius = radius
          this.rgb = rgb

          // 随机运动速度
          this.vx = Math.random() * speed
          this.vy = Math.random() * speed
          this.sinValue = Math.random()
        }
        animate(ctx, stageWidth, stageHeight) {
          this.sinValue += 0.01
          // Math.sin(this.sinValue) 值-1~1之间,让圆变大后又缩小
          this.radius += Math.sin(this.sinValue)
          this.x += this.vx
          this.y += this.vy

          // 超出边界
          if (this.x < -10) {
            this.vx *= -1
          } else if (this.x > stageWidth + 10) {
            this.vx *= -1
          }
          if (this.y < -10) {
            this.vy *= -1
          } else if (this.y > stageHeight + 10) {
            this.vy *= -1
          }
          // 绘图
          ctx.beginPath()
          // 径向渐变
          const g = ctx.createRadialGradient(this.x, this.y, this.radius * 0.01, this.x, this.y, this.radius)
          // 渐变颜色过渡
          g.addColorStop(0, `rgba(${this.rgb.r},${this.rgb.g},${this.rgb.b},1)`)
          g.addColorStop(1, `rgba(${this.rgb.r},${this.rgb.g},${this.rgb.b},0)`)
          ctx.fillStyle = g
          ctx.arc(this.x, this.y, this.radius, 0, PI2, false)
          ctx.fill()
        }
      }
    </script>
  </body>
</html>