Skip to content

Instantly share code, notes, and snippets.

@anon987654321
Created February 5, 2026 03:01
Show Gist options
  • Select an option

  • Save anon987654321/5ed08c69393c816f7eb223e7738e3337 to your computer and use it in GitHub Desktop.

Select an option

Save anon987654321/5ed08c69393c816f7eb223e7738e3337 to your computer and use it in GitHub Desktop.
Cosmic Web

Cosmic Web

Webs traced out by drawing lines between orbs in close proximity to one another.

At any point you can right-click and save the image, if you want a fancy new background :) It will have a transparent backdrop, so you might need to custom make a black layer in another tool--gimp, photoshop, etc.

Change screensize in the parameters.

A Pen by Ryan Thavi on CodePen.

License.

<canvas id="canvas-bottom"></canvas>
<canvas id="canvas-top"></canvas>
(function(){
'use strict';
// Configuration options
var opts = {
background: 'black',
numberOrbs: 100, // increase with screen size. 50 to 100 for my 2560 x 1400 monitor
maxVelocity: 2.5, // increase with screen size--dramatically affects line density. 2-3 for me
orbRadius: 1, // keep small unless you really want to see the dots bouncing. I like <= 1.
minProximity: 100, // controls how close dots have to come to each other before lines are traced
initialColorAngle: 7, // initialize the color angle, default = 7
colorFrequency: 0.3, // 0.3 default
colorAngleIncrement: 0.009, // 0.009 is slow and even
globalAlpha: 0.010, //controls alpha for lines, but not dots (despite the name)
manualWidth: false, // Default: false, change to your own custom width to override width = window.innerWidth. Yes i know I'm mixing types here, sue me.
manualHeight: false// Default: false, change to your own custom height to override height = window.innerHeight
};
// Canvas globals
var canvasTop, linecxt, canvasBottom, cxt, width, height, animationFrame;
// Global objects
var orbs;
// Orb object - these are the guys that bounce around the screen.
// We will draw lines between these dots, but that behavior is found
// in the Orbs container object
var Orb = (function() {
// Constructor
function Orb(radius, color) {
var posX = randBetween(0, width);
var posY = randBetween(0, height);
this.position = new Vector(posX, posY);
var velS = randBetween(0, opts.maxVelocity); // Velocity scalar
this.velocity = Vector.randomDirection().multiply(velS).noZ();
this.radius = radius;
this.color = color;
}
// Orb methods
Orb.prototype = {
update: function() {
// position = position + velocity
this.position = this.position.add(this.velocity);
// bounce if the dot reaches the edge of the container.
// this can be EXTREMELY buggy with large dot radiuses, but it works for this
// drawing.
if (this.position.x + this.radius >= width || this.position.x - this.radius <= 0) {
this.velocity.x = this.velocity.x * -1;
}
if (this.position.y + this.radius >= height || this.position.y - this.radius <= 0) {
this.velocity.y = this.velocity.y * -1;
}
},
display: function() {
cxt.beginPath();
cxt.fillStyle = this.color;
cxt.ellipse(this.position.x, this.position.y, this.radius, this.radius, 0, 0, 2*Math.PI, false);
cxt.fill();
cxt.closePath();
},
run: function() {
this.update();
this.display();
}
};
return Orb;
})();
// Orbs object - this is a container that manages all of the individual Orb objects.
// In addition, this object holds the color phasing and line-drawing functionality,
// since it already iterates over all the orbs once per frame anyway.
var Orbs = (function() {
// Constructor
function Orbs(numberOrbs, radius, initialColorAngle, globalAlpha, colorAngleIncrement, colorFrequency) {
this.orbs = [];
this.colorAngle = initialColorAngle;
this.colorAngleIncrement = colorAngleIncrement;
this.globalAlpha = globalAlpha;
this.colorFrequency = colorFrequency;
this.color = null;
for (var i = 0; i < numberOrbs; i++) {
this.orbs.push(new Orb(radius, this.color));
}
}
Orbs.prototype = {
run: function() {
this.phaseColor();
for (var i = 0; i < this.orbs.length; i++) {
for (var j = i + 1; j < this.orbs.length; j++) {
// we only want to compare this orb to orbs which are further along in the array,
// since any that came before will have already been compared to this orb.
this.compare(this.orbs[i], this.orbs[j]);
}
this.orbs[i].color = this.color;
this.orbs[i].run();
}
},
compare: function(orbA, orbB) {
// Get the distance between the two orbs.
var distance = Math.abs(orbA.position.subtract(orbB.position).length());
if (distance <= opts.minProximity) {
// the important thing to note here is that we're drawing this onto '#canvas-top'
// since we want to preserve everything drawn to that layer.
linecxt.beginPath();
linecxt.strokeStyle = this.color;
linecxt.globalAlpha = this.globalAlpha;
linecxt.moveTo(orbA.position.x, orbA.position.y);
linecxt.lineTo(orbB.position.x, orbB.position.y);
linecxt.stroke();
linecxt.closePath();
}
},
phaseColor: function() {
// color component = sin(freq * angle + phaseOffset) => (between -1 and 1) * 127 + 128
var r = Math.floor(Math.sin(this.colorFrequency*this.colorAngle + Math.PI*0/3) * 127 + 128);
var g = Math.floor(Math.sin(this.colorFrequency*this.colorAngle + Math.PI*2/3) * 127 + 128);
var b = Math.floor(Math.sin(this.colorFrequency*this.colorAngle + Math.PI*4/3) * 127 + 128);
this.color = 'rgba(' + r + ', ' + g + ', ' + b + ', 1)';
this.colorAngle += this.colorAngleIncrement;
}
};
return Orbs;
})();
// This function is called once and only once to kick off the code.
// It links DOM objects like the canvas to the respective global variable.
function initialize() {
canvasTop = document.querySelector('#canvas-top'); // this canvas is for the lines between dots
canvasBottom = document.querySelector('#canvas-bottom'); // this canvas is for the dots that bounce around
linecxt = canvasTop.getContext('2d');
cxt = canvasBottom.getContext('2d');
window.addEventListener('resize', resize, false);
resize();
}
// This function is called after initialization and window resize.
function resize() {
width = opts.manualWidth ? opts.manualWidth : window.innerWidth;
height = opts.manualHeight ? opts.manualHeight : window.innerHeight;
setup();
}
// after window resize we need to
function setup() {
canvasTop.width = width;
canvasTop.height = height;
canvasBottom.width = width;
canvasBottom.height = height;
//fillBackground(linecxt); // Enable this line if you want to save an image of the drawing.
fillBackground(cxt);
orbs = new Orbs(opts.numberOrbs, opts.orbRadius, opts.initialColorAngle, opts.globalAlpha, opts.colorAngleIncrement, opts.colorFrequency);
// If we hit this line, it was either via initialization procedures (which means animationFrame is undefined)
// or through window resize, in which case we need to cancel the old draw loop and make a new one.
if (animationFrame !== undefined) { cancelAnimationFrame(animationFrame); }
draw();
}
// Notice that we only fillBackground on one of the two canvases. This is because we want to animate
// the dot layer (we don't want to leave trails left by the dots), but preserve the line layer.
function draw() {
fillBackground(cxt);
orbs.run();
// Update the global animationFrame variable -- this enables to cancel the redraw loop on resize
animationFrame = requestAnimationFrame(draw);
}
// generic background fill function
function fillBackground(context) {
context.fillStyle = opts.background;
context.fillRect(0, 0, width, height);
}
// get random float between two numbers, inclusive
function randBetween(low, high) {
return Math.random() * (high - low) + low;
}
// get random INT between two numbers, inclusive
function randIntBetween(low, high) {
return Math.floor(Math.random() * (high - low + 1) + low);
}
// Start the code already, dammit!
initialize();
})();
<script src="https://codepen.io/rthavi/pen/pePJaY.js"></script>
html,
body {
margin: 0;
}
body {
overflow: hidden;
}
canvas {
position: absolute;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment