<!-- Please ā¤ this and follow me if you like it! -->




























<canvas id="c"></canvas>



<!-- Code added to every pen -->
<div class="credits"><a class="other-pens" href="https://codepen.io/PickJBennett/" target="_blank" rel="external noopener"><svg class="credits-logo codepen-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="0" height="0"><path d="M255.8 87.1c-.05-.32-.1-.63-.2-.94-.04-.18-.1-.35-.16-.53-.08-.27-.17-.54-.28-.8l-.25-.54c-.1-.3-.2-.5-.3-.8l-.3-.5-.4-.7-.4-.5-.5-.6-.4-.5-.6-.5-.5-.4c-.03-.1-.1-.1-.2-.2l-117-78c-3.7-2.5-8.5-2.5-12.2 0L5 79.7c-.06.04-.1.1-.17.13l-.5.36-.63.5-.48.4c-.2.2-.4.4-.55.6l-.37.5-.45.6-.32.5c-.14.26-.26.5-.37.75l-.25.52c-.1.23-.2.5-.2.8l-.2.6c-.1.3-.15.6-.2.9l-.1.47c-.1.5-.1.95-.1 1.44v78c0 .5 0 1 .1 1.46l.1.48c.07.3.1.62.2.93l.2.5c.08.3.17.6.27.8l.25.53.4.73.3.5c.1.23.3.44.5.65l.3.46c.2.2.3.4.5.6.2.15.3.3.5.4l.6.55.5.4.15.18 116.95 78c1.84 1.25 3.95 1.87 6.1 1.86 2.13 0 4.24-.63 6.1-1.86l117-78c.1-.05.1-.1.2-.15l.5-.37.6-.5c.15-.1.3-.27.44-.4.2-.2.4-.4.57-.6.16-.17.3-.3.4-.5.18-.2.34-.4.48-.65l.3-.5.36-.76c.1-.14.14-.3.22-.5l.3-.8.15-.53c.1-.3.12-.6.2-.9 0-.2.05-.34.1-.5.04-.5.1-.95.1-1.44V89c0-.5-.07-.96-.1-1.44 0-.17-.1-.3-.1-.47zM128 154l-38.9-26 38.9-26.02L166.9 128 128 154.02zm-11-71.15l-47.7 31.9L30.8 89 117 31.57v51.3zM49.52 128l-27.5 18.4v-36.8L49.5 128zm19.8 13.24l47.68 31.9v51.3L30.8 167l38.5-25.74zm69.68 31.9l47.7-31.9 38.5 25.74-86.2 57.46v-51.3zM206.47 128L234 109.6v36.8L206.46 128zm-19.78-13.23L139 82.87v-51.3L225.2 89l-38.5 25.77z"/></svg>Check out my other pens</a> <a id="js-tweet-this" href="https://twitter.com/PickJBennett" target="_blank" rel="external noopener"><svg class="credits-logo twitter-logo" id="js-twitter-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 273.39 222.18" width="0" height="0"><path d="M273.4 26.3c-10.07 4.46-20.88 7.48-32.22 8.83 11.58-6.94 20.47-17.93 24.66-31.03C255 10.53 243 15.2 230.22 17.7 220 6.8 205.42 0 189.28 0c-30.98 0-56.1 25.1-56.1 56.1 0 4.38.5 8.66 1.46 12.77-46.6-2.34-87.94-24.67-115.6-58.6-4.84 8.28-7.6 17.92-7.6 28.2 0 19.45 9.9 36.62 24.95 46.68-9.2-.3-17.85-2.8-25.41-7.02-.02.24-.02.47-.02.7 0 27.2 19.33 49.86 45 55-4.7 1.3-9.67 1.98-14.78 1.98-3.62 0-7.13-.35-10.56-1 7.14 22.28 27.85 38.5 52.4 38.95-19.2 15.05-43.38 24-69.66 24-4.53 0-9-.25-13.38-.77 24.82 15.9 54.3 25.2 85.98 25.2 103.17 0 159.6-85.47 159.6-159.6 0-2.42-.07-4.84-.18-7.24 10.96-7.9 20.47-17.8 28-29.04z"/></svg></a></div>
/* Please ā¤ this and follow me if you like it! */




























:root {
	box-sizing: border-box;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	-o-user-select: none;
	user-select: none;
	-webkit-tap-highlight-color: transparent;
	-moz-tap-highlight-color: transparent;
	-ms-tap-highlight-color: transparent;
	-o-tap-highlight-color: transparent;
	tap-highlight-color: transparent;
}

*,
:before,
:after {
	box-sizing: inherit;
}

html,
body,
canvas {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	display: block;
	width: 100%;
	height: 100%;
	-webkit-contain: strict;
	-moz-contain: strict;
	-ms-contain: strict;
	-o-contain: strict;
	contain: strict;
}

html {
	overflow: hidden;
}

body {
	margin: 0;
	overflow: auto;
	overflow-x: hidden;
	overflow-y: scroll;
	-webkit-overflow-scrolling: touch;
	-moz-overflow-scrolling: touch;
	-ms-overflow-scrolling: touch;
	-o-overflow-scrolling: touch;
	overflow-scrolling: touch;
	-ms-overflow-style: ms-autohiding-scrollbar;
}

/* Pen-specific styles here */





.credits {
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	font-size: 130%;
	text-align: center;
	width: 100%;
	padding: 2rem 0.2rem 2rem;
}

.credits a {
	display: inline-block;
	color: #3bb1bc;
	font-family: Arial, sans-serif;
	line-height: 1.5em;
	text-decoration: none !important;
	white-space: nowrap;
	margin: 0 1.2em;
	padding: 0.6em 0;
	transition: color 200ms;
}

.credits a:hover {
	color: #000;
	cursor: pointer;
}

.credits-logo {
	fill: currentColor;
	display: inline-block;
	vertical-align: bottom;
	width: 1.6em;
	height: 1.6em;
	margin: 0 0.5em 0 0;
}
/* Please ā¤ this and follow me if you like it! */




























class Dots {
	constructor(width, height, spacing) {
		this.spacing = spacing;
		this.dots = [];
		this.alphaStep = 1 / 10;
		this.cols = Math.floor(width / spacing);
		this.rows = Math.floor(height / spacing);

		const canvas = document.createElement('canvas'),
			ctx = canvas.getContext('2d');

		canvas.width = width;
		canvas.height = height;
		this.canvas = canvas;
		this.ctx = ctx;

		this.draw();
	}
	draw() {
		const ctx = this.ctx,
			spacing = this.spacing;

		ctx.fillStyle = 'rgba(24, 129, 141, .1)';
		this.dots = Array.apply(null, Array(this.cols)).map((n, x) => {
			return Array.apply(null, Array(this.rows)).map((p, y) => {
				let dot = {
					opacity: 0.1,
					x: x * spacing,
					y: y * spacing
				};

				ctx.fillRect(dot.x, dot.y, 1, 1);
				return dot;
			});
		});
	}
	ghost() {
		const ghostDots = document.createElement('canvas');
		ghostDots.width = this.canvas.width;
		ghostDots.height = this.canvas.height;

		const dotsCtx = ghostDots.getContext('2d');
		dotsCtx.fillStyle = 'rgb(24, 129, 141)';
		this.dots.forEach(col => {
			col.forEach(dot => {
				dotsCtx.fillRect(dot.x, dot.y, 1, 1);
			});
		});

		return ghostDots;
	}
}

class Circuits {
	constructor(width, height, size, minLength, maxLength) {
		this.size = size;
		this.width = width;
		this.height = height;
		this.cols = ~~(width / size);
		this.rows = ~~(height / size);

		this.scene = Array.apply(null, Array(this.cols)).map(() => new Col(this.rows));

		this.collection = [];
		this.minLength = minLength;
		this.maxLength = maxLength;


		this.populate();
		this.draw();
	}
	draw() {
		const canvas = document.createElement('canvas'),
			ctx = canvas.getContext('2d'),
			size = this.size;

		canvas.width = this.width;
		canvas.height = this.height;

		ctx.strokeStyle = 'rgba(59, 177, 188, 1)';
		ctx.lineWidth = Math.round(size / 10);
		this.collection.forEach(circuit => {
			let point = [circuit.start[0], circuit.start[1]],
				path = circuit.path;

			ctx.beginPath();
			ctx.moveTo(point[0] * size + size / 2 + path[0][0] * size / 4, point[1] * size + size / 2 + path[0][1] * size / 4);
			path.forEach((dir, index) => {
				point[0] += dir[0];
				point[1] += dir[1];
				if (index === path.length - 1) {
					ctx.lineTo(point[0] * size + size / 2 - dir[0] * size / 4, point[1] * size + size / 2 - dir[1] * size / 4);
				} else {
					ctx.lineTo(point[0] * size + size / 2, point[1] * size + size / 2);
				}
			});
			ctx.stroke();
		});

		ctx.lineWidth = ~~(this.size / 5);
		ctx.strokeStyle = 'rgba(59, 177, 188, .6)';
		this.collection.forEach(circuit => {
			ctx.beginPath();
			ctx.arc(circuit.start[0] * size + size / 2, circuit.start[1] * size + size / 2, size / 4, 0, 2 * Math.PI, false);
			ctx.stroke();
			ctx.beginPath();
			ctx.arc(circuit.end[0] * size + size / 2, circuit.end[1] * size + size / 2, size / 4, 0, 2 * Math.PI, false);
			ctx.stroke();
		});

		this.canvas = canvas;
	}
	populate() {
		const size = this.size;

		let start = null,
			n = 1000,
			maxLength = this.maxLength,
			minLength = this.minLength,
			length = 0,
			dir = null;

		while ((start = this.getStart()) && n--) {
			length = minLength + ~~(Math.random() * (maxLength - minLength));
			dir = this.getDir(start);

			this.setUsed(start[0], start[1]);
			// if we can move from this point
			if (dir[0] !== 0 || dir[1] !== 0) {
				let circuit = new Circuit(start, size),
					moving = true,
					path = [start[0], start[1]],
					coords = [start[0], start[1]];
				length--;

				while (moving && length) {
					circuit.path.push(dir);
					circuit.coords.push([path[0], path[1]]);

					path[0] += dir[0];
					path[1] += dir[1];

					// set used
					this.setUsed(path[0], path[1]);
					// get new dir
					dir = this.getDir(path, dir);
					if (dir[0] === 0 && dir[1] === 0) {
						moving = false;
					}
					length--;
				}

				if (circuit.path.length >= minLength) {
					circuit.end = path;
					circuit.coords.push([path[0], path[1]]);

					let speed = Math.random() * 0.5 + 0.5;

					circuit.things.push(things.create(circuit, speed * 1));

					if (circuit.path.length > maxLength / 3) {
						speed = Math.random() * 0.5 + 0.5;
						circuit.things.push(things.create(circuit, -speed, circuit.path.length * size));
					}

					if (circuit.path.length > maxLength / 1.5) {
						speed = Math.random() * 0.5 + 0.5 * (Math.random() >= 0.5 ? -1 : 1);
						circuit.things.push(things.create(circuit, speed, Math.random() * circuit.path.length * size));
					}

					circuit.length = circuit.path.length * size;
					this.collection.push(circuit);
				}
			}
		}
	}
	getStart() {
		let found = false,
			col = null,
			row = null,
			free = [],
			result = false;

		const scene = this.scene;

		// select cols with free cell
		scene.forEach((col, index) => {
			if (col.free) {
				free.push(index);
			}
		});

		if (free.length) {
			// pick one of the col
			col = this.pickOne(free);

			// select the free cells in the col
			free.length = 0;
			scene[col].rows.forEach((row, index) => {
				if (row === 0) {
					free.push(index);
				}
			});

			// pick one of the cell
			row = this.pickOne(free);

			result = [col, row];
		}
		return result;
	}
	pickOne(array) {
		return array[~~(Math.random() * array.length)];
	}
	setUsed(x, y) {
		this.scene[x].rows[y] = 1;
		this.scene[x].free--;
	}
	isAvailable(x, y) {
		const scene = this.scene;
		let result = false;
		if (typeof scene[x] !== 'undefined') {
			if (typeof scene[x].rows[y] !== 'undefined') {
				if (scene[x].rows[y] === 0) {
					result = true;
				}
			}
		}
		return result;
	}

	// get direction
	// if a current direction is given, there is 50% chances to go in the same
	getDir(fromPoint, oldDir = null) {
		const possibleX = [],
			possibleY = [],
			result = [0, 0];

		if (oldDir && Math.random() <= 0.5) {
			if (this.isAvailable(fromPoint[0] + oldDir[0], fromPoint[1] + oldDir[1])) {
				return oldDir;
			}
		}

		// Xs
		if (this.isAvailable(fromPoint[0] - 1, fromPoint[1])) {
			possibleX.push(-1);
		}
		if (this.isAvailable(fromPoint[0] + 1, fromPoint[1])) {
			possibleX.push(1);
		}

		// Ys
		if (this.isAvailable(fromPoint[0], fromPoint[1] - 1)) {
			possibleY.push(-1);
		}
		if (this.isAvailable(fromPoint[0], fromPoint[1] + 1)) {
			possibleY.push(1);
		}

		if (possibleX.length && Math.random() < 0.5) {
			result[0] = this.pickOne(possibleX);
		} else if (possibleY.length) {
			result[1] = this.pickOne(possibleY);
		}

		return result;
	}
}

class Col {
	constructor(rows) {
		this.rows = Array.apply(null, Array(rows)).map(() => 0);
		this.free = rows;
	}
}

class Circuit {
	constructor(start, size) {
		this.start = start;
		this.cellSize = size;
		this.path = [];
		this.end = null;
		this.things = [];
		this.length = 0;
		this.coords = [];
	}
}

class Things {
	constructor(width, height) {
		this.width = width;
		this.height = height;

		this.canvas = document.createElement('canvas');
		this.canvas.width = width;
		this.canvas.height = height;
		this.ctx = this.canvas.getContext('2d');

		this.collection = [];
	}
	create(circuit, velocity, done = 0) {
		const thing = new Thing(circuit, velocity, done);
		this.collection.push(thing)
		return thing;
	}
	update() {
		this.collection.forEach(thing => {
			thing.update();
		});
	}
	draw() {
		const ctx = this.ctx,
			radius = this.lightRadius,
			diameter = radius * 2,
			space = radius / 3;

		let radial = null,
			diffX = null,
			diffY = null;
		ctx.clearRect(0, 0, this.width, this.height);
		this.collection.forEach(thing => {
			thing.update();
			radial = this.ghostRadial;
			diffX = diffY = radius;
			if (thing.distFromSister() <= space) {
				radial = this.ghostSuperRadial;
				diffX = radial.width / 2;
				diffY = radial.height / 2;
			}
			ctx.drawImage(radial, thing.x - diffX, thing.y - diffY, radial.width, radial.height);
		});

		ctx.save();
		ctx.globalCompositeOperation = 'destination-in';
		ctx.drawImage(this.dotsGhost, 0, 0);
		ctx.restore();

		ctx.save();
		ctx.globalCompositeOperation = 'source-over';
		ctx.fillStyle = '#afe3e9';
		this.collection.forEach(thing => {
			ctx.beginPath();
			ctx.arc(thing.x, thing.y, radius / 6, 0, 2 * Math.PI, false);
			ctx.fill();
		});
		ctx.restore();
	}
	setDotsGhost(canvas) {
		this.dotsGhost = canvas;
	}
	setLight(lightRadius) {
		this.lightRadius = lightRadius;

		this.ghostRadial = document.createElement('canvas');
		this.ghostRadial.width = lightRadius * 2;
		this.ghostRadial.height = lightRadius * 2;

		const radialCtx = this.ghostRadial.getContext('2d');
		let gradient = radialCtx.createRadialGradient(lightRadius, lightRadius, lightRadius, lightRadius, lightRadius, 0);
		gradient.addColorStop(0, "rgba(24, 129, 141, 0)");
		gradient.addColorStop(1, "rgba(24, 129, 141, .6)");

		radialCtx.fillStyle = gradient;
		radialCtx.fillRect(0, 0, lightRadius * 2, lightRadius * 2);


		// star
		this.ghostSuperRadial = document.createElement('canvas');
		const radWidth = this.ghostSuperRadial.width = lightRadius * 15;
		const radHeight = this.ghostSuperRadial.height = lightRadius * 20;

		const superRadialCtx = this.ghostSuperRadial.getContext('2d');

		gradient = superRadialCtx.createRadialGradient(radWidth / 2, radHeight / 2, radWidth / 2, radWidth / 2, radHeight / 2, 0);
		gradient.addColorStop(0, "rgba(37, 203, 223, 0)");
		gradient.addColorStop(1, "rgba(37, 203, 223,  .4)");

		superRadialCtx.fillStyle = gradient;

		superRadialCtx.beginPath();
		superRadialCtx.moveTo(radWidth / 2 + lightRadius / 6, radHeight / 2 - lightRadius / 3);
		superRadialCtx.lineTo(radWidth, 0);
		superRadialCtx.lineTo(radWidth / 2 + lightRadius / 3, radHeight / 2 - lightRadius / 6);
		superRadialCtx.lineTo(3 * radWidth / 4, radHeight / 2);
		superRadialCtx.lineTo(radWidth / 2 + lightRadius / 3, radHeight / 2 + lightRadius / 6);
		superRadialCtx.lineTo(radWidth, radHeight);
		superRadialCtx.lineTo(radWidth / 2 + lightRadius / 6, radHeight / 2 + lightRadius / 3);
		superRadialCtx.lineTo(radWidth / 2, 3 * radHeight / 4);
		superRadialCtx.lineTo(radWidth / 2 - lightRadius / 6, radHeight / 2 + lightRadius / 3);
		superRadialCtx.lineTo(0, radHeight);
		superRadialCtx.lineTo(radWidth / 2 - lightRadius / 3, radHeight / 2 + lightRadius / 6);
		superRadialCtx.lineTo(radWidth / 4, radHeight / 2);
		superRadialCtx.lineTo(radWidth / 2 - lightRadius / 3, radHeight / 2 - lightRadius / 6);
		superRadialCtx.lineTo(0, 0);
		superRadialCtx.lineTo(radWidth / 2 - lightRadius / 6, radHeight / 2 - lightRadius / 3);
		superRadialCtx.lineTo(radWidth / 2, radHeight / 4);
		superRadialCtx.lineTo(radWidth / 2 + lightRadius / 6, radHeight / 2 - lightRadius / 3);
		superRadialCtx.fill();
	}
}


class Thing {
	constructor(circuit, velocity, done = 0) {
		this.circuit = circuit;
		this.velocity = velocity;
		this.done = done;
		this.x = 0;
		this.y = 0;
		this.dots = [];
	}
	update() {
		const circuit = this.circuit,
			size = circuit.cellSize;

		let x = 0,
			y = 0;
		// update this
		const length = circuit.length,
			start = circuit.start,
			end = circuit.end,
			path = circuit.path;
		this.done += this.velocity;
		if (this.done <= 0) {
			this.done = 0;
			this.velocity = -this.velocity;
		} else if (this.done >= length) {
			this.done = length;
			this.velocity = -this.velocity;
		}

		if (this.done <= size / 2) {
			x = (start[0] * size + size / 2) + this.done * path[0][0];
			y = (start[1] * size + size / 2) + this.done * path[0][1];
		} else if (this.done > (length - size / 2)) {
			x = (end[0] * size + size / 2) - (length - this.done) * path[path.length - 1][0];
			y = (end[1] * size + size / 2) - (length - this.done) * path[path.length - 1][1];
		} else {

			const index = ~~(this.done / size),
				done = this.done - index * size,
				dir = [path[index][0], path[index][1]],
				point = circuit.coords[index];

			x = point[0] * size + size / 2 + done * dir[0];
			y = point[1] * size + size / 2 + done * dir[1];
		}
		x = ~~x;
		y = ~~y;
		this.x = x;
		this.y = y;
	}
	distFromSister() {
		const circuit = this.circuit;
		let dist = Infinity,
			tmp = null;
		circuit.things.forEach(thing => {
			if (thing !== this) {
				tmp = Math.abs(thing.done - this.done);
				if (tmp < dist) {
					dist = tmp;
				}
			}
		});

		return dist;
	}
}


class Background {
	constructor(width, height) {
		this.width = width;
		this.height = height;
	}
	getBackground() {
		const canvas = document.createElement('canvas'),
			ctx = canvas.getContext('2d');

		canvas.width = this.width;
		canvas.height = this.height;

		ctx.fillStyle = '#000';
		ctx.fillRect(0, 0, this.width, this.height);

		ctx.drawImage(dots.canvas, 0, 0);
		ctx.drawImage(circuits.canvas, 0, 0);

		return canvas;
	}
}




// background init
const bgCanvas = document.getElementById('c'),
	width = bgCanvas.width = window.innerWidth,
	height = bgCanvas.height = window.innerHeight,
	bgCtx = bgCanvas.getContext('2d');

// dots
const dots = new Dots(width, height, 2);

// things
const things = new Things(width, height);
// get dot ghost
// it will serve as a clip canvas for the gradients to only show where there is originally dots in the background
things.setDotsGhost(dots.ghost());
things.setLight(dots.spacing * 4);

// circuits
const maxLength = 16,
	minLength = 3,
	cellSize = 10,
	circuits = new Circuits(width, height, cellSize, minLength, maxLength);

// background first and only draw
const background = new Background(width, height),
	staticBG = background.getBackground();
bgCtx.drawImage(staticBG, 0, 0);

// animation
const canvas = document.createElement('canvas'),
	ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
document.body.appendChild(canvas);

function loop() {
	ctx.clearRect(0, 0, width, height);
	// draw things
	things.draw();
	ctx.drawImage(things.canvas, 0, 0);

	requestAnimationFrame(loop);
}

// draw bg (dots + circuit) on the main canvas
loop();



// Working with credits

var cpCreditsUrl = "https://codepen.io/dievardump/details/pyOMeN";
var cpCreditsTitle = "Circuits";
var cpCreditsTwitter = document.getElementById("js-tweet-this");
cpCreditsTwitter.href = "https://twitter.com/intent/tweet?text=" + encodeURI(cpCreditsTitle) + "&url=" + encodeURI(cpCreditsUrl) + "&via=dievardump&related=PickJBennett";
cpCreditsTwitter.innerHTML += "Tweet This Pen";

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.