<canvas id="canvas"></canvas>
body, html {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
cursor: crosshair;
}
/*
Johan Karlsson, 2020
https://twitter.com/DonKarlssonSan
MIT License, see Details View
*/
class Circle {
constructor(x, y) {
this.x = x;
this.y = y;
this.r = 10;
this.done = false;
}
draw() {
this.drawCircle(this.x, this.y, this.r * 0.92, this.r / 8);
let d = mouseVector.sub(new Vector(this.x, this.y));
let p = d.div(d.getLength()).mult(this.r / 4);
this.drawCircle(this.x + p.x, this.y + p.y, this.r * 0.6, this.r / 16);
p.multTo(2.2);
this.drawCircle(this.x + p.x, this.y + p.y, this.r * 0.3, this.r / 24);
}
drawCircle(x, y, r, lineWidth) {
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2);
ctx.stroke();
}
}
let canvas;
let ctx;
let w, h;
let circles;
let mouseVector;
let mouseHasLeft;
function setup() {
mouseHasLeft = true;
canvas = document.querySelector("#canvas");
ctx = canvas.getContext("2d");
reset();
window.addEventListener("resize", () => {
reset();
});
canvas.addEventListener("click", reset);
canvas.addEventListener("mousemove", mouseMove);
canvas.addEventListener("mouseleave", mouseLeave);
canvas.addEventListener("mouseenter", mouseEnter)
mouseVector = new Vector(w * 0.75, h * 0.75);
}
function reset() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
resetCircles();
packCircles();
}
function clear() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, w, h);
ctx.strokeStyle = "white";
}
function dist(x1, y1, x2, y2) {
return Math.hypot(x1 - x2, y1 - y2);
}
function addCircles() {
let nrOfTries = 0;
let wasAdded;
do {
wasAdded = false;
let x = Math.random() * w;
let y = Math.random() * h;
if(validPos(x, y)) {
wasAdded = true;
let c = new Circle(x, y);
circles.push(c);
}
nrOfTries++;
} while (!wasAdded && nrOfTries < 50)
}
function validPos(x, y) {
for(let i = 0; i < circles.length; i++) {
let current = circles[i];
let d = dist(x, y, current.x, current.y);
if(d - 10 < current.r) {
return false;
}
}
return true;
}
function canGrow(circle) {
for(let i = 0; i < circles.length; i++) {
let current = circles[i];
if(circle !== current) {
let d = dist(circle.x, circle.y, current.x, current.y);
if(d - 4 <= circle.r + current.r) {
return false;
}
}
}
return true;
}
function resetCircles() {
circles = [];
}
function packCircles() {
let nrOfTries = w * h / 400;
for(let i = 0; i < nrOfTries; i++) {
if(i % 4 === 0) {
addCircles();
}
circles.filter(c => !c.done).forEach(c => {
if(canGrow(c)) {
c.r += 2;
} else {
c.done = true;
}
});
}
}
function drawCircles() {
circles.forEach(c => c.draw());
}
function draw(now) {
requestAnimationFrame(draw);
clear();
fakeMoveMouse(now);
drawCircles();
}
function mouseMove(event) {
mouseVector.x = event.clientX;
mouseVector.y = event.clientY;
}
function mouseLeave() {
mouseHasLeft = true;
}
function mouseEnter() {
mouseHasLeft = false;
}
function fakeMoveMouse(now) {
if(mouseHasLeft) {
mouseVector.x = Math.cos(now/1000) * w/3 + w/2;
mouseVector.y = Math.sin(now/1000) * h/3 + h/2;
}
}
setup();
draw(1);
View Compiled
This Pen doesn't use any external CSS resources.