<canvas id="canvas" width="800" height="800"></canvas>
<div>
	<label for="slid">tails</label>
	<input id="slid" type="range" min="0" max="50" value="4" onchange="changeTails(this.value)" oninput="changeTails(this.value)" />
	<button onclick="restart()">restart</button>
</div>
#canvas {
	width: 400px;
	height: 400px;
}

body {
	font-family: sans-serif;
	color: #fff; 
	background-color: #000;
	height: 100vh;
	display: flex;
	justify-content: center;
	align-items: center;
}

div > * {
	display: block;
	text-align: center;
	margin: 0 auto;
}

div > *:not(first-child) {
	margin-top: 8px;
}

div {
	margin-left: 20px;
}
const force = 2;
const minDistance = 100;

const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");

const cols = 30;
const rows = cols * canvas.height / canvas.width;
const squareSize = canvas.width / cols;

const Dot = function(x, y) {
	this.x = x;
	this.y = y;
	this.dx = 0;
	this.dy = 0;
};

let dots;

const initDots = () => {
	dots = [];
	for (let i = 0; i <= cols; i += 2) {
		for (let j = 0; j <= rows; j += 2) {
			const dot = new Dot(i * squareSize, j * squareSize);
			dots.push(dot);
		}
	}
};

initDots();

function rotate(p1, c, deg) {
	const dx = p1.x - c.x;
	const dy = p1.y - c.y;
	const ang = Math.atan(dy / dx) + deg;
	const hyp = Math.sqrt(Math.pow(Math.abs(dx), 2) + Math.pow(Math.abs(dy), 2));
	return {
		x: c.x + hyp * Math.cos(ang),
		y: c.y + hyp * Math.sin(ang)
	};
}

requestAnimationFrame(function() {
	frame(context, 0, rotate);
});

context.strokeStyle = "#fff";
context.fillStyle = "rgba(0,0,0,0.046)";

let firstCount = 0;

function frame(context, val, func) {
	let i;
	for (i = 0; i < dots.length; i++) {
		for (let j = i + 1; j < dots.length; j++) {
			let dist = Math.sqrt(
				Math.pow(dots[j].x - dots[i].x, 2) + Math.pow(dots[j].y - dots[i].y, 2)
			);
			dist = Math.max(minDistance, dist);
			const dist3 = force / (dist * dist * dist);
			const dx = (dots[j].x - dots[i].x) * dist3;
			const dy = (dots[j].y - dots[i].y) * dist3;

			dots[i].dx += dx;
			dots[i].dy += dy;

			dots[j].dx -= dx;
			dots[j].dy -= dy;
		}
	}

	context.fillRect(0, 0, canvas.width, canvas.height);

	for (i = 0; i < dots.length; i++) {
		const dot = dots[i];
		dot.x += dot.dx;
		dot.y += dot.dy;
		context.beginPath();
		context.arc(dot.x, dot.y, 0.75, 0, 2 * Math.PI);
		context.stroke();
	}

	window.requestAnimationFrame(function() {
		frame(context, val + 0.1, func);
	});
}

const changeTails = v => {
	context.fillStyle = `rgba(0,0,0,${0.05 - 0.001 * v})`;
};

const restart = () => {
	const fs = context.fillStyle;
	context.fillStyle = "#000";
	context.fillRect(0, 0, canvas.width, canvas.height);
	context.fillStyle = fs;
	initDots();
};
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.