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