鼠标 canvas动画
<!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>