<canvas>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
const STG = {
nbParticles: 50
}
let W, H
const cvs = document.querySelector("canvas")
const ctx = cvs.getContext("2d")
cvs.width = W = window.innerWidth
cvs.height = H = window.innerHeight
// create an offscreen canvas which will be used to draw the subtrate from the particles
const offCvs = document.createElement("canvas")
const offCtx = offCvs.getContext("2d")
offCvs.width = W
offCvs.height = H
// initialize the particles
const particles = []
for (let i = 0; i < STG.nbParticles; i++) {
particles[i] = {
x: Math.random() * W,
y: Math.random() * H,
vx: (Math.random() - 0.5) * .5,
vy: (Math.random() - 0.5) * .5
}
}
// toroidal topology (coordinates that loop on the edges)
const toroidal = (x, y) => {
if (x > W) x%= W
else if (x < 0) x = W + x
if (y > H) y%= H
else if (y < 0) y = H + y
return { x, y }
}
const update = () => {
// move the particles
for (const p of particles) {
const { x, y } = toroidal(p.x + p.vx, p.y + p.vy)
p.x = x
p.y = y
}
// apply some decay to the offscreen canvas
offCtx.globalCompositeOperation = "normal"
offCtx.globalAlpha = 0.05
offCtx.fillStyle = `rgba(0, 0, 0, 1)`
offCtx.fillRect(0, 0, W, H)
// draw to the substrate map (ie the offscreen canvas)
offCtx.globalAlpha = 0.2
offCtx.globalCompositeOperation = "lighter"
for (const p of particles) {
const grad = offCtx.createRadialGradient(p.x, p.y, 3, p.x, p.y, 50)
grad.addColorStop(0, "#555555")
grad.addColorStop(1, "transparent")
offCtx.beginPath()
offCtx.arc(p.x, p.y, 50, 0, 2 * Math.PI)
offCtx.fillStyle = grad
offCtx.fill()
}
}
const draw = () => {
// draw the substrate
ctx.drawImage(offCvs, 0, 0)
// draw the particles
ctx.fillStyle = "red"
for (const p of particles) {
ctx.beginPath()
ctx.arc(p.x, p.y, 4, 0, 2*Math.PI)
ctx.fill()
}
}
const loop = () => {
requestAnimationFrame(loop)
update()
draw()
}
loop()
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.