<canvas id="canvas"></canvas>
<!--
Click adds a new gravity point.
Click and drag a gravity point to move it.
Ctrl + Click on a gravity point to remove it.
Shift + Click anywhere to spawn a few dead satellites back.
The gravity points gain mass / size as they consume satellites.
-->
html, body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
}
canvas {
position: absolute;
}
/*
requestAnimationFrame shim
*/
window.requestAnimationFrame = (function () {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
function Particle(x, y, s, c, m) {
// positions
this.x = x;
this.y = y;
// velocities
this.vx = 0;
this.vy = 0;
// size
this.s = s;
// color
this.c = c; // as of right now, unused.
// mass
this.m = m;
// alive
this.alive = true;
}
Particle.prototype = {
constructor: Particle,
gravitate: function (host) {
var dx = host.x - this.x,
dy = host.y - this.y,
d = Math.sqrt(Math.abs(dx * dx + dy * dy)),
a = Math.atan2(dx, dy) * (180 / Math.PI),
ac = host.m / Math.pow(d, 2);
if (d < host.s / 2 + this.s / 2) {
this.alive = false;
host.m += this.m * .033;
host.s += this.s * .033;
}
var accel = (host.m / Math.pow(d, 2));
this.vx += Math.sin(a * (Math.PI / 180)) * accel;
this.vy += Math.cos(a * (Math.PI / 180)) * accel;
},
update: function () {
this.x += this.vx;
this.y += this.vy;
// if it gets away from us then kill it.
if (this.x < -width || this.x > width * 2 || this.y < -height || this.y > height * 2) {
this.alive = false;
}
},
reset: function () {
var p = start_position(0, width, 0, height),
a = Math.atan2(p.x - width / 2, p.y - height / 2) * (180 / Math.PI);
this.x = p.x;
this.y = p.y;
this.vx = -Math.sin((a+Math.random()*5) * (Math.PI / 180)) * .14;
this.vy = -Math.cos((a+Math.random()*5) * (Math.PI / 180)) * .14;
this.alive = true;
},
render: function (context) {
context.beginPath();
context.arc(this.x, this.y, this.s / 2, 0, Math.PI * 2, true);
context.fillStyle = this.c;
context.fill();
context.closePath();
}
};
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
width,height,particles,hosts,moving;
var stats = new Stats();
setTimeout(init, 100);
function init() {
height = canvas.height = document.body.offsetHeight;
width = canvas.width = document.body.offsetWidth;
stats.setMode(0); // 0: fps, 1: ms
// Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
particles = [];
hosts = [];
for (var i = 0; i < 1024; i++) {
var x = Math.random() * width,
y = Math.random() * height;
var p = new Particle(x, y, Math.random() * 3 + 1, 'black', 3),
a = Math.atan2(p.x - width / 2, p.y - height / 2) * (180 / Math.PI);
p.vx = Math.random() * 1.5 - .75;
p.vy = Math.random() * 1.5 - .75;
particles.push(p);
}
make_host();
for (var i = 0; i < 2; i++) {
var x = Math.random() * (width / 2) + (width / 4),
y = Math.random() * (height / 2) + (height / 4);
make_host(x, y);
}
canvas.onmousedown = handle_mousedown;
canvas.onmouseup = handle_mouseup;
update();
render();
}
function handle_mousedown(e) {
var x = e.clientX,
y = e.clientY;
if (e.shiftKey) {
var limit = 12;
for (var i = 0; i < particles.length; i++) {
if (particles[i].alive) {
continue;
}
if (--limit <= 0) {
return;
}
particles[i].reset();
}
}
for (var i = 0; i < hosts.length; i++) {
var host = hosts[i];
if (host.x > x - host.s/2 && host.x < x + host.s/2 && host.y > y - host.s/2 && host.y < y + host.s/2) {
if (e.ctrlKey) {
hosts.splice(i, 1);
} else {
moving = i;
canvas.addEventListener('mousemove', handle_mousemove, false);
}
return;
}
}
make_host(e.clientX, e.clientY);
}
function handle_mouseup(e) {
if (typeof moving === 'undefined') {
return;
}
moving = undefined;
canvas.removeEventListener('mousemove', handle_mousemove, false);
}
function handle_mousemove(e) {
hosts[moving].x = e.clientX;
hosts[moving].y = e.clientY;
}
function make_host(x, y) {
var mass = Math.random() * 50 + 50,
size = mass / 5;
hosts.push({
x: x || width / 2,
y: y || height / 2,
m: mass,
s: size,
c: 'red'
});
}
function update() {
for (var i = 0; i < particles.length; i++) {
if (!particles[i].alive) {
continue;
}
for (var j = 0; j < hosts.length; j++) {
particles[i].gravitate(hosts[j]);
}
particles[i].update();
}
setTimeout(update, 1000 / 60);
}
function start_position(x1, x2, y1, y2, buffer) {
buffer = Math.random() * (buffer || 100);
if (Math.random() > .5) {
return {
x: (Math.random() * (x2 - x1)) + x1,
y: Math.random() > .5 ? -buffer : height + buffer
}
}
return {
x: Math.random() > .5 ? -buffer : width + buffer,
y: (Math.random() * (y2 - y1)) + y1
}
}
function render() {
stats.begin();
context.fillStyle = 'rgba(255, 255, 255, .1)';
context.fillRect(0, 0, width, height);
for (var j = 0; j < hosts.length; j++) {
context.beginPath();
context.arc(hosts[j].x, hosts[j].y, hosts[j].s / 2, 0, Math.PI * 2, true);
context.fillStyle = hosts[j].c;
context.fill();
context.closePath();
}
for (var i = 0; i < particles.length; i++) {
if (!particles[i].alive) {
continue;
}
particles[i].render(context);
}
requestAnimationFrame(render);
stats.end();
}
setInterval(function() {
if( particles.filter(function(a){ return a.alive }).length === 0 ) {
handle_mousedown({shiftKey: true});
// init();
}
}, 60000);
This Pen doesn't use any external CSS resources.