<!-- 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.