*{margin:0;overflow:hidden;}
const canvas = document.createElement('canvas')
const c = canvas.getContext('2d')

canvas.width = innerWidth
canvas.height = innerHeight
document.body.append(canvas)

c.fillStyle = 'black'
c.fillRect(0, 0, canvas.width, canvas.height)

class Floater {
  constructor(x, y, size, draw) {
    this.x = x
    this.y = y
    let t = Math.random() * 7
    let v = Math.random() * 1 + .2
    this.v = v 
    this.vx = Math.cos(t) * v
    this.vy = Math.sin(t) * v
    this.draw = draw
    this.size = size

    t = Math.random() * 7
    v = Math.random() * .2

    this.svx = Math.cos(t) * v
    this.svy = Math.sin(t) * v
  }
  update() {
    this.x += this.vx
    this.y += this.vy


    this.draw(this)

    if (this.x < -this.size) {
      this.x = innerWidth + this.size
    }
    if (this.x > innerWidth + this.size) {
      this.x = -this.size
    }
    if (this.y < -this.size) {
      this.y = innerHeight + this.size
    }
    if (this.y > innerHeight + this.size) {
      this.y = -this.size
    }
  }
}

function bigCirc(t) {
  c.fillStyle = '#888'
  c.strokeStyle = 'white'
  c.lineWidth = 5
  c.beginPath()
  c.arc(t.x, t.y, t.size, 0, 7)
  c.fill()
  c.stroke()
  if (Math.random() < .01) {
    const a = Math.random() * 7
    t.v = Math.random() * 1
    t.dx = Math.cos(a) * t.v
    t.dy = Math.sin(a) * t.v
    t.turn = true
  }
  if (t.turn) {
    t.vx += (t.dx - t.vx) / 52
    t.vy += (t.dy - t.vy) / 52
  }
}

function littleCirc(t) {

  const dx = this.x - big.x
  const dy = this.y - big.y
  const dist = Math.sqrt(dx * dx + dy * dy)
  if (dist < big.size * 2) {
    const t = Math.atan2(dy, dx) / Math.PI * 180
    this.vx += Math.cos(t) * this.v * .2
    this.vy += Math.sin(t) * this.v * .2
  } else {
    this.vx += ((-big.vx + t.svx) - this.vx) / 2
    this.vy += ((-big.vy + t.svy) - this.vy) / 2
  }
  this.vx *= .999
  this.vy *= .999
  c.fillStyle = 'white'
  c.beginPath()
  c.arc(t.x, t.y, t.size, 0, 7)
  c.fill()
}

const big = new Floater(
  innerWidth / 2, innerHeight / 2,
  100,
  bigCirc
)

let littles = []
let NUM = 100
for (let i = 0; i < 100; i++) {
  littles.push(new Floater(
    Math.random() * innerWidth,
    Math.random() * innerHeight,
    10,
    littleCirc
  ))
} 

onresize = e => {
  canvas.width = innerWidth
  canvas.height = innerHeight
  c.globalAlpha = 1
  c.globalCompositeOperation = 'source-over'
  c.fillStyle = 'black'
  c.fillRect(0, 0, canvas.width, canvas.height)
}
onresize()

function loop() {

  c.globalCompositeOperation = 'source-over'
  c.fillStyle = 'rgba(0, 0, 0, 0.15)'
  c.fillRect(0, 0, canvas.width, canvas.height)


  c.globalCompositeOperation = 'hard-light'
  big.update()


  littles.forEach(l => l.update())

  c.globalAlpha = .5
  c.globalCompositeOperation = 'darken'
  c.drawImage(canvas, 0, 0, canvas.width + 4, canvas.height + 4)
  requestAnimationFrame(loop)
}
loop()

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.