JavaScript
语言:
JaveScriptBabelCoffeeScript
确定
(function() {
'use strict';
// Configuration options
var opts = {
background: 'black',
numberOrbs: 50, // 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 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, // 7 initializes the color phaser on red
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
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();
})();