鼠标 canvas动画

发布时间 2023-07-28 16:48:10作者: 玉文
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <canvas id="canvasId" height="500" width="500"></canvas>
</body>
</html>
<script>
const c = document.getElementById('canvasId')
const ctx = c.getContext('2d')
const s = c.width = c.height = 500

const opts = {
  particles: 200,
  particleBaseSize: 4,
  particleAddedSize: 1,
  particleMaxSize: 5,
  particleBaseLight: 5,
  particleAddedLight: 30,
  particleBaseBaseAngSpeed: 0.001,
  particleAddedBaseAngSpeed: 0.001,
  particleBaseVariedAngSpeed: 0.0005,
  particleAddedVariedAngSpeed: 0.0005,
  sourceBaseSize: 3,
  sourceAddedSize: 3,
  sourceBaseAngSpeed: -0.01,
  sourceVariedAngSpeed: 0.005,
  sourceBaseDist: 130,
  sourceVariedDist: 50,
  particleTemplateColor: 'hsla(hue,80%,light%,alp)',
  repaintColor: 'rgba(24,24,24,.1)',
  enableTrails: false
}
const util = {
  square: x => x * x,
  tau: 6.2831853071795864769252867665590057683943
}
const particles = []
const source = new Source()
let tick = 0

function Particle () {
  this.dist = (Math.sqrt(Math.random())) * s / 2
  this.rad = Math.random() * util.tau

  this.baseAngSpeed = opts.particleBaseBaseAngSpeed + opts.particleAddedBaseAngSpeed * Math.random()
  this.variedAngSpeed = opts.particleBaseVariedAngSpeed + opts.particleAddedVariedAngSpeed * Math.random()
  this.size = opts.particleBaseSize + opts.particleAddedSize * Math.random()
}
Particle.prototype.step = function () {
  const angSpeed = this.baseAngSpeed + this.variedAngSpeed * Math.sin(this.rad * 7 + tick / 100)
  this.rad += angSpeed

  const x = this.dist * Math.cos(this.rad)
  const y = this.dist * Math.sin(this.rad)

  const squareDist = util.square(x - source.x) + util.square(y - source.y)
  const sizeProp = s ** (1 / 2) / squareDist ** (1 / 2)
  const color = opts.particleTemplateColor
    .replace('hue', this.rad / util.tau * 360 + tick)
    .replace('light', opts.particleBaseLight + sizeProp * opts.particleAddedLight)
    .replace('alp', 0.8)

  ctx.fillStyle = color
  ctx.beginPath()
  ctx.arc(x, y, Math.min(this.size * sizeProp, opts.particleMaxSize), 0, util.tau)
  ctx.fill()
}

function Source () {
  this.x = 0
  this.y = 0
  this.rad = Math.random() * util.tau
}
Source.prototype.step = function () {
  if (!this.mouseControlled) {
    const angSpeed = opts.sourceBaseAngSpeed + Math.sin(this.rad * 6 + tick / 100) * opts.sourceVariedAngSpeed
    this.rad += angSpeed

    const dist = opts.sourceBaseDist + Math.sin(this.rad * 5 + tick / 100) * opts.sourceVariedDist

    this.x = dist * Math.cos(this.rad)
    this.y = dist * Math.sin(this.rad)
  }

  ctx.fillStyle = 'white'
  ctx.beginPath()
  ctx.arc(this.x, this.y, 1, 0, util.tau)
  ctx.fill()
}

function anim () {
  window.requestAnimationFrame(anim)

  ++tick

  if (!opts.enableTrails) { ctx.globalCompositeOperation = 'source-over' }

  ctx.fillStyle = opts.repaintColor
  ctx.fillRect(0, 0, s, s)

  ctx.globalCompositeOperation = 'lighter'

  if (particles.length < opts.particles) { particles.push(new Particle()) }

  ctx.translate(s / 2, s / 2)

  source.step()

  particles.map(particle => particle.step())
  ctx.translate(-s / 2, -s / 2)
}

ctx.fillStyle = '#222'
ctx.fillRect(0, 0, s, s)
anim()

c.addEventListener('mousemove', e => {
  const bbox = c.getBoundingClientRect()

  source.x = e.clientX - bbox.left - s / 2
  source.y = e.clientY - bbox.top - s / 2
  source.mouseControlled = true
})
c.addEventListener('mouseleave', e => {
  const bbox = c.getBoundingClientRect()

  source.x = e.clientX - bbox.left - s / 2
  source.y = e.clientY - bbox.top - s / 2

  source.rad = Math.atan(source.y / source.x)
  if (source.x < 0) { source.rad += Math.PI }

  source.mouseControlled = false
})

</script>