<!-- 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";
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.