{
const options = {
width: 600,
height: 600,
pad: 20,
maxRadius: 4,
count: 1000,
speed: .003,
shiftSpeed: .5,
minSize: .2,
maxSize: 1,
minOffset: .25,
maxOffset: .75,
minSpeed: 1,
maxSpeed: 1,
dirRatio: .7,
color: 'hsl(0,0%,100%)',
bgColor: 'hsla(0,0%,0%,.3)',
};
const {
width: w,
height: h,
pad, maxRadius, count, color, bgColor, speed, shiftSpeed,
minSize, maxSize, minSpeed, maxSpeed, dirRatio, minOffset, maxOffset,
} = options;
const cx = w/2, cy = h/2, rMax = Math.min(cx, cy) - pad - maxRadius;
const ctx = DOM.context2d(w, h);
ctx.canvas.style.maxWidth = '100%';
const circles = Array(count).fill().map((v,i,arr) => ({
a: mix(0, TAU, rng()),
t: mix(minOffset, maxOffset, rng()),
size: mix(minSize, maxSize, rng()),
speed: mix(minSpeed, maxSpeed, rng()) * (rng() >= dirRatio ? 1 : -1),
}));
ctx.fillStyle = color;
while(true) {
yield draw();
step(speed);
}
function draw() {
clear(ctx, w, h, bgColor);
ctx.beginPath();
circles.forEach((c, i) => {
let t = Math.sin(c.t * TAU) / 2 + .5;
t = Math.abs(t) ** shiftSpeed * Math.sign(t) * i / count;
const x = cx + Math.cos(c.a) * rMax * t;
const y = cy + Math.sin(c.a) * rMax * t;
circle(ctx, x, y, c.size * maxRadius);
});
ctx.fill();
return ctx.canvas;
}
function step(s = 1) {
circles.forEach((c, i, arr) => {
c.t = (c.t + c.speed * s) % 1;
});
}
}