canvas烟花实现

2 分钟阅读

闲来无事的时候想起了一个文章里看的canvas制作烟花,今天暂且贴上,改日给大家做进一步的剖析

HTML部分

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        canvas {
            position: absolute;
        }
    </style>
</head>

<body>
    <canvas id="myCanvas" width="1913" height="796"></canvas>
    <canvas id="firework" width="1913" height="796"></canvas>
    <script src="./main.js"></script>
</body>
</html>

JS部分

PS:由于是一个小demo 并没有做模块拆分 都写在一个里了 希望大家自己做的时候可以分解一下 有助于自己对模块化的理解

javascript
const Config = {
  width: window.innerWidth,
  height: window.innerHeight,
  skyColor: 'hsla(hue, 60%, light%, 0.2)',
  fireworkTime: {min: 30, max: 150},
  fireworkOpt: {
    x: undefined,
    y: undefined,
    xEnd: undefined,
    yEnd: undefined,
    count: 500,
    wait: 10
  }
}

class Firework {
  constructor ({x, y = Config.height, xEnd, yEnd, count = Config.fireworkOpt.count, wait} = {}) {
    this.x = x || Config.width / 8 + Math.random() * Config.width * 3 / 4
    this.y = y
    this.xEnd = xEnd || this.x
    this.yEnd = yEnd || Config.width / 8 + Math.random() * Config.width * 3 / 8
    this.size = 2
    this.velocity = -3
    this.opacity = 0.8
    this.wait = wait || 30 + Math.random() * 30
    this.count = count
    this.particles = []
    this.createParticles()
    this.status = 1
    this.hue = 360 * Math.random() | 0
    this.color = `hsla(${this.hue},80%,60%,1)`
  }
  getSkyColor () {
    const skyColor = {
      lightness: this.status === 3 ? this.opacity : 0,
      hue: this.hue
    }
    return skyColor
  }
  createParticles () {
    for (let i = 0; i < this.count; ++i) {
      this.particles.push(new Particle({x: this.xEnd, y: this.yEnd}))
    }
  }

  rise () {
    this.y += this.velocity
    this.velocity += 0.005
    if (this.y - this.yEnd <= 50) {
      this.opacity = (this.y - this.yEnd) / 50
    }
    if (this.y <= this.yEnd) {
      this.status = 2
    }
  }

  draw (ctx) {
    switch (this.status) {
      case 1:
        ctx.save()
        ctx.beginPath()
        ctx.globalCompositeOperation = 'lighter'
        ctx.globalAlpha = this.opacity
        ctx.translate(this.x, this.y)
        ctx.scale(0.8, 2.3)
        ctx.translate(-this.x, -this.y)
        ctx.fillStyle = this.color
        ctx.arc(this.x + Math.sin(Math.PI * 2 * Math.random()) / 1.2, this.y, this.size, 0, Math.PI * 2, false)
        ctx.fill()
        ctx.restore()

        this.rise()
        return true
        break
      case 2:
        if (--this.wait <= 0) {
          this.opacity = 1
          this.status = 3
        }
        return true
        break
      case 3:
        ctx.save()
        ctx.globalCompositeOperation = 'lighter'
        ctx.globalAlpha = this.opacity
        ctx.fillStyle = this.color
        for (let i = 0; i < this.particles.length; i++) {
          this.particles[i].draw(ctx)
        }
        ctx.restore()
        this.opacity -= 0.005
        return this.opacity >= 0
        break
      default:
        return false
    }
  }
}

class Particle {
  constructor ({x, y, size = 1, radius = 6} = {}) {
    this.x = x
    this.y = y
    this.size = size
    this.rate = Math.random()
    this.angle = Math.PI * 2 * Math.random()
    this.vx = radius * Math.cos(this.angle) * this.rate
    this.vy = radius * Math.sin(this.angle) * this.rate
  }

  go () {
    this.x += this.vx
    this.y += this.vy
    this.vy += 0.02

    this.vx *= 0.98
    this.vy *= 0.98
  }

  draw (ctx) {
    this.go()
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false)
    ctx.fill()
  }
}

const canvas = {
  init () {
    this.skyColor = {
      lightness: 0,
      hue: 210
    }
    this.setProperty()
    this.drawBg()

    this.loop()
  },
  setProperty () {
    this.fireworks = []
    this.width = Config.width
    this.height = Config.height
    this.fireworkTime = (Config.fireworkTime.min + (Config.fireworkTime.max - Config.fireworkTime.min) * Math.random()) | 0
    this.bgCtx = document.querySelector('#myCanvas').getContext('2d')
    this.fireworkCtx = document.querySelector('#firework').getContext('2d')
  },
  drawBg () {
    this.bgCtx.fillStyle = 'hsla(210, 60%, 5%, 0.9)'
    this.bgCtx.fillRect(0, 0, this.width, this.height)
  },
  loop () {
    // this.fireworkCtx.clearRect(0, 0, this.width, this.height)
    requestAnimationFrame(this.loop.bind(this))
    this.fireworkCtx.fillStyle = Config.skyColor.replace('light', 5 + this.skyColor.lightness * 15).replace('hue', this.skyColor.hue)
    this.fireworkCtx.fillRect(0, 0, this.width, this.height)
    if (--this.fireworkTime <= 0) {
      this.fireworks.push(new Firework(Config.fireworkOpt))
      this.fireworkTime = (Config.fireworkTime.min + (Config.fireworkTime.max - Config.fireworkTime.min) * Math.random()) | 0
    }
    for (let i = this.fireworks.length - 1; i >= 0; --i) {
      !this.fireworks[i].draw(this.fireworkCtx) && this.fireworks.splice(i, 1)
    }
    for (let i = this.fireworks.length - 1; i >= 0; --i) {
      this.skyColor = this.skyColor.lightness >= (this.fireworks[i].getSkyColor().lightness + 50) ? this.skyColor : this.fireworks[i].getSkyColor()
      !this.fireworks[i].draw(this.fireworkCtx) && this.fireworks.splice(i, 1)

      console.log(this.fireworks.length)
    }
  }
}
canvas.init()

大家可以自行观察一下效果 并对此做一些理解 希望对大家有所帮助