<canvas id="view"></canvas>
body {
  background: #000;
}

#view {
  position: fixed;
  top: 0;
  left: 0;
}
const WIDTH = window.innerWidth,
  HEIGHT = window.innerHeight,
  POINT_AMOUNT = 5,
  FREQUENCY = 1,
  FPS_HIGH = 60,
  FPS_LOW = 12,
  LINE_API = new PIXI.Graphics(),
  APP = new PIXI.Application({
    view: document.querySelector("#view"),
    width: window.innerWidth,
    height: window.innerHeight,
    backgroundColor: 0x0000000,
    antialias: true
  });

let colorPhase = 1,
  increment = 1,
  state = {
    speed: 6,
    lineAmount: 18,
    lineThickness: 1,
    windows98TrueLowFps: false
  };

// Add line graphic to stage
APP.stage.addChild(LINE_API);

// Controls
const gui = new dat.GUI();
gui.add(state, "speed", 1, 20, 1).listen();
gui.add(state, "lineAmount", 1, 120, 1).listen();
gui.add(state, "lineThickness", 1, 5, 1).listen();
gui.add(state, "windows98TrueLowFps").onChange((value) => {
  const newFps = value ? FPS_LOW : FPS_HIGH;
  APP.ticker.maxFPS = newFps;
});

// A point in the visualizer
function CreatePoint() {
  this.dirX = Math.random() > .5 ? .5: -.5;
  this.dirY = Math.random() > .5 ? .5: -.5;
  this.positions = [{ x: Math.random() * WIDTH, y: Math.random() * HEIGHT }];
}

// Sine color incrementer
function colorChange(phase) {
  function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
  }

  let center = 128,
    width = 127,
    red = Math.floor(
      Math.sin(FREQUENCY * colorPhase + 2 + phase) * width + center
    ),
    green = Math.floor(
      Math.sin(FREQUENCY * colorPhase + 0 + phase) * width + center
    ),
    blue = Math.floor(
      Math.sin(FREQUENCY * colorPhase + 4 + phase) * width + center
    );

  return `${componentToHex(red)}${componentToHex(green)}${componentToHex(blue)}`;
}

// Setup
let points = [];
for (let i = 0; i < POINT_AMOUNT; i++) {
  points[i] = new CreatePoint();
}

// Ticker
const ticker = new PIXI.Ticker();

ticker.add(() => {
  colorPhase += 0.01;
  LINE_API.clear();  
  
  
  // LOOP "DOWN" THE POINT POSITIONS
  for (let i = 0; i < points[0].positions.length; i++) {
    // append new values to position array (if first level)
    if(i == 0) {
      for(let p of points) {
        // push new value onto positions
        const topPosPoint = p.positions[0];
                
        // BOUNDARY LOGIC
        if(topPosPoint.x < 0 || topPosPoint.x > WIDTH) {
          p.dirX *= -1;
        }
        if(topPosPoint.y < 0 || topPosPoint.y > HEIGHT) {
          p.dirY *= -1;
        }
        
        // Calculate new position and unshift our positions
        let newPos = { x : topPosPoint.x + p.dirX * state.speed, y : topPosPoint.y + p.dirY * state.speed };
        p.positions.unshift(newPos);
        
        // TRIM the line amount
        if(p.positions.length > state.lineAmount) {
          p.positions.length = state.lineAmount;
        }
      }
    }
    
    // Before we loop, reset where we start our line
    LINE_API.lineStyle(state.lineThickness, `0x${colorChange(colorPhase)}`)
            .moveTo(points[0].positions[i].x, points[0].positions[i].y);

    // NOW LOOP "AROUND" THE POINTS
    for (let j = 1; j < points.length; j++) {
      const currPoint = points[j].positions[i];
      
      LINE_API.lineTo(currPoint.x, currPoint.y);
      
      // Close our circle because we're on the last item
      if(j == points.length - 1) {
        LINE_API.lineTo(points[0].positions[i].x, points[0].positions[i].y);
      }
    }
    LINE_API.endFill();

  }
}, PIXI.UPDATE_PRIORITY.LOW);
ticker.start();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.9/pixi.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.4.2/gsap.min.js