Created
October 24, 2012 17:12
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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