Skip to content

Instantly share code, notes, and snippets.

@xav76
Created October 24, 2012 17:12
Show Gist options
  • Save xav76/3947392 to your computer and use it in GitHub Desktop.
Save xav76/3947392 to your computer and use it in GitHub Desktop.
A CodePen by Hakim El Hattab. Magnetic - An old <canvas> particle experiment of mine.
<p>Double-click to add new nodes. Drag to move them. <br>Change skin: <a id="prevSkin" href="#">Previous</a> / <a id="nextSkin" href="#">Next</a>.</p>
<canvas id='world'></canvas>
Magnetic = new function() {
var SCREEN_WIDTH = window.innerWidth;
var SCREEN_HEIGHT = window.innerHeight;
var MAGNETS_AT_START = 4;
var PARTICLES_PER_MAGNET = 20;
var MAGNETIC_FORCE_THRESHOLD = 300;
var canvas;
var context;
var particles = [];
var magnets = [];
var mouseX = (window.innerWidth - SCREEN_WIDTH);
var mouseY = (window.innerHeight - SCREEN_HEIGHT);
var mouseIsDown = false;
var mouseDownTime = 0;
var skinIndex = 0;
var skins = [
{ glowA: 'rgba(0,200,250,0.3)', glowB: 'rgba(0,200,250,0.0)', particleFill: '#ffffff', fadeFill: 'rgba(22,22,22,.6)', useFade: true },
{ glowA: 'rgba(230,0,0,0.3)', glowB: 'rgba(230,0,0,0.0)', particleFill: '#ffffff', fadeFill: 'rgba(22,22,22,.6)', useFade: true },
{ glowA: 'rgba(0,230,0,0.3)', glowB: 'rgba(0,230,0,0.0)', particleFill: 'rgba(0,230,0,0.7)', fadeFill: 'rgba(22,22,22,.6)', useFade: true },
{ glowA: 'rgba(0,0,0,0.3)', glowB: 'rgba(0,0,0,0.0)', particleFill: '#333333', fadeFill: 'rgba(255,255,255,.6)', useFade: true },
{ glowA: 'rgba(0,0,0,0.0)', glowB: 'rgba(0,0,0,0.0)', particleFill: '#333333', fadeFill: 'rgba(255,255,255,.2)', useFade: true },
{ glowA: 'rgba(230,230,230,0)', glowB: 'rgba(230,230,230,0.0)', particleFill: '#ffffff', fadeFill: '', useFade: false }
];
this.init = function() {
canvas = document.getElementById( 'world' );
if (canvas && canvas.getContext) {
context = canvas.getContext('2d');
// Register event listeners
window.addEventListener('mousemove', documentMouseMoveHandler, false);
window.addEventListener('mousedown', documentMouseDownHandler, false);
window.addEventListener('mouseup', documentMouseUpHandler, false);
document.getElementById( 'prevSkin' ).addEventListener('click', previousSkinClickHandler, false);
document.getElementById( 'nextSkin' ).addEventListener('click', nextSkinClickHandler, false);
window.addEventListener('resize', windowResizeHandler, false);
createMagnets();
windowResizeHandler();
setInterval( loop, 1000 / 60 );
}
}
function createMagnets() {
var w = 300;
var h = 300;
for (var i = 0; i < MAGNETS_AT_START; i++) {
var position = {
x: ( SCREEN_WIDTH - w ) * 0.5 + (Math.random() * w),
y: ( SCREEN_HEIGHT - h ) * 0.5 + (Math.random() * h)
};
createMagnet( position );
}
}
function createMagnet( position ) {
var m = new Magnet();
m.position.x = position.x;
m.position.y = position.y;
magnets.push( m );
createParticles( m.position );
}
function createParticles( position ) {
for (var i = 0; i < PARTICLES_PER_MAGNET; i++) {
var p = new Particle();
p.position.x = position.x;
p.position.y = position.y;
p.shift.x = position.x;
p.shift.y = position.y;
p.color = skins[skinIndex].particleFill;
particles.push( p );
}
}
function documentMouseMoveHandler(event) {
mouseX = event.clientX - (window.innerWidth - SCREEN_WIDTH) * .5;
mouseY = event.clientY - (window.innerHeight - SCREEN_HEIGHT) * .5;
}
function documentMouseDownHandler(event) {
event.preventDefault();
mouseIsDown = true;
if( new Date().getTime() - mouseDownTime < 300 ) {
// The mouse was pressed down twice with a < 300 ms interval: add a magnet
createMagnet( { x: mouseX, y: mouseY } );
mouseDownTime = 0;
}
mouseDownTime = new Date().getTime();
for( var i = 0, len = magnets.length; i < len; i++ ) {
magnet = magnets[i];
if( distanceBetween( magnet.position, { x: mouseX, y: mouseY } ) < magnet.orbit * .5 ) {
magnet.dragging = true;
break;
}
}
}
function documentMouseUpHandler(event) {
mouseIsDown = false;
for( var i = 0, len = magnets.length; i < len; i++ ) {
magnet = magnets[i];
magnet.dragging = false;
}
}
function previousSkinClickHandler(event) {
event.preventDefault();
--skinIndex;
updateSkin();
}
function nextSkinClickHandler(event) {
event.preventDefault();
++skinIndex;
updateSkin();
}
function updateSkin() {
skinIndex = skinIndex < 0 ? skins.length-1 : skinIndex;
skinIndex = skinIndex > skins.length-1 ? 0 : skinIndex;
for (var i = 0, len = particles.length; i < len; i++) {
particles[i].color = skins[skinIndex].particleFill;
}
}
function windowResizeHandler() {
SCREEN_WIDTH = window.innerWidth;
SCREEN_HEIGHT = window.innerHeight;
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
canvas.style.position = 'absolute';
canvas.style.left = (window.innerWidth - SCREEN_WIDTH) * .5 + 'px';
canvas.style.top = (window.innerHeight - SCREEN_HEIGHT) * .5 + 'px';
}
function loop() {
if( skins[skinIndex].useFade) {
context.fillStyle = skins[skinIndex].fadeFill;
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
}
else {
context.clearRect(0,0,canvas.width,canvas.height);
}
var particle, magnet;
var i, j, ilen, jlen;
// Render the magnets
for( j = 0, jlen = magnets.length; j < jlen; j++ ) {
magnet = magnets[j];
if( magnet.dragging ) {
magnet.position.x += ( mouseX - magnet.position.x ) * 0.2;
magnet.position.y += ( mouseY - magnet.position.y ) * 0.2;
}
// Increase the size of the magnet center point depending on # of connections
magnet.size += ( (magnet.connections/3) - magnet.size ) * 0.025;
magnet.size = Math.max(magnet.size,2);
var gradientFill = context.createRadialGradient(magnet.position.x,magnet.position.y,0,magnet.position.x,magnet.position.y,magnet.size*10);
gradientFill.addColorStop(0,skins[skinIndex].glowA);
gradientFill.addColorStop(1,skins[skinIndex].glowB);
context.beginPath();
context.fillStyle = gradientFill;
context.arc(magnet.position.x, magnet.position.y, magnet.size*10, 0, Math.PI*2, true);
context.fill();
context.beginPath();
context.fillStyle = '#00000000';
context.arc(magnet.position.x, magnet.position.y, magnet.size, 0, Math.PI*2, true);
context.fill();
magnet.connections = 0;
}
// Render the particles
for (i = 0, ilen = particles.length; i < ilen; i++) {
particle = particles[i];
var currentDistance = -1;
var closestDistance = -1;
var closestMagnet = null;
var force = { x: 0, y: 0 };
// For each particle, we check what the closes magnet is
for( j = 0, jlen = magnets.length; j < jlen; j++ ) {
magnet = magnets[j];
currentDistance = distanceBetween( particle.position, magnet.position ) - ( magnet.orbit * 0.5 );
if( particle.magnet != magnet ) {
var fx = magnet.position.x - particle.position.x;
if( fx > -MAGNETIC_FORCE_THRESHOLD && fx < MAGNETIC_FORCE_THRESHOLD ) {
force.x += fx / MAGNETIC_FORCE_THRESHOLD;
}
var fy = magnet.position.y - particle.position.y;
if( fy > -MAGNETIC_FORCE_THRESHOLD && fy < MAGNETIC_FORCE_THRESHOLD ) {
force.y += fy / MAGNETIC_FORCE_THRESHOLD;
}
}
if( closestMagnet == null || currentDistance < closestDistance ) {
closestDistance = currentDistance;
closestMagnet = magnet;
}
}
if( particle.magnet == null || particle.magnet != closestMagnet ) {
particle.magnet = closestMagnet;
}
closestMagnet.connections += 1;
// Rotation
particle.angle += particle.speed;
// Translate towards the magnet position
particle.shift.x += ( (closestMagnet.position.x+(force.x*8)) - particle.shift.x) * particle.speed;
particle.shift.y += ( (closestMagnet.position.y+(force.y*8)) - particle.shift.y) * particle.speed;
// Appy the combined position including shift, angle and orbit
particle.position.x = particle.shift.x + Math.cos(i+particle.angle) * (particle.orbit*particle.force);
particle.position.y = particle.shift.y + Math.sin(i+particle.angle) * (particle.orbit*particle.force);
// Limit to screen bounds
particle.position.x = Math.max( Math.min( particle.position.x, SCREEN_WIDTH-particle.size/2 ), particle.size/2 );
particle.position.y = Math.max( Math.min( particle.position.y, SCREEN_HEIGHT-particle.size/2 ), particle.size/2 );
// Slowly inherit the cloest magnets orbit
particle.orbit += ( closestMagnet.orbit - particle.orbit ) * 0.1;
context.beginPath();
context.fillStyle = particle.color;
context.arc(particle.position.x, particle.position.y, particle.size/2, 0, Math.PI*2, true);
context.fill();
}
}
function distanceBetween(p1,p2) {
var dx = p2.x-p1.x;
var dy = p2.y-p1.y;
return Math.sqrt(dx*dx + dy*dy);
}
};
function Particle() {
this.size = 0.5+Math.random()*3.5;
this.position = { x: 0, y: 0 };
this.shift = { x: 0, y: 0 };
this.angle = 0;
this.speed = 0.01 + (this.size/4) * 0.015;
this.force = 1 - (Math.random()*0.15);
this.color = '#ffffff';
this.orbit = 1;
this.magnet = null;
}
function Magnet() {
this.orbit = 100;
this.position = { x: 0, y: 0 };
this.dragging = false;
this.connections = 0;
this.size = 1;
}
Magnetic.init();
body {
background-color: #000;
padding: 0;
margin: 0;
overflow: hidden;
}
p {
position: absolute;
z-index: 99;
color: #ccc;
margin: 10px;
padding: 0;
font-family: Arial;
font-size: 11px;
}
a {
color: #24cad6;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment