|
(function(){ |
|
|
|
// cross browser requestAnimationFrame loop to handle the animation looping |
|
var rAF = (function(){ |
|
return window.requestAnimationFrame || |
|
window.webkitRequestAnimationFrame || |
|
window.mozRequestAnimationFrame || |
|
window.oRequestAnimationFrame || |
|
window.msRequestAnimationFrame || |
|
function( callback ){ |
|
window.setTimeout(callback, 1000 / 60); |
|
}; |
|
})(); |
|
|
|
// average particles per 4 grid blocks |
|
var winWidth, winHeight, |
|
gridCols, gridRows, |
|
startCount, particles, tracer, |
|
canvas, ctx, |
|
targetFPS = 60, |
|
avgFPS = 60, |
|
timer = (new Date()).getTime(), |
|
lastMouseMove = (new Date()).getTime(), |
|
frameBuffer = [60,60,60,60,60,60,60,60,60,60], |
|
FPS = 0, |
|
tick = 1, |
|
fillStyles = [], |
|
friction = 0.97, |
|
initialized = false, initializing = true; |
|
|
|
// ------------------------ // |
|
// #### Particle Class #### // |
|
// ------------------------ // |
|
function Particle(){ |
|
// reference to the current particle |
|
var _p = this; |
|
|
|
// set this particle's initial location to a random |
|
// place on the screen within the bounds of the window. |
|
_p.x = winWidth/2; |
|
_p.y = winHeight/2; |
|
|
|
// the radius of this particle |
|
_p.tracer = { |
|
x: winWidth/2, |
|
y: winHeight/2 |
|
}; |
|
|
|
// start this particle with velocity |
|
_p.velx = Math.random() * 4 - 2; |
|
_p.vely = Math.random() * 4 - 2; |
|
} |
|
// Setup a common draw function that will be called |
|
// by all members of the Particle class |
|
Particle.prototype.draw = function(){ |
|
ctx.fillRect(this.x, this.y, 2, 2); |
|
}; |
|
Particle.prototype.update = function(tick){ |
|
var _p = this, |
|
_vel, |
|
_t = _p.tracer, |
|
diffX, diffY, |
|
iAmLeader = _p === particles[0] && timer - lastMouseMove > 5000, |
|
jump = iAmLeader ? 20 : 8; |
|
|
|
if (iAmLeader) { |
|
tracer.x = _p.x; |
|
tracer.y = _p.y; |
|
} else { |
|
diffX = _t.x - _p.x; |
|
diffY = _t.y - _p.y; |
|
} |
|
|
|
if (Math.abs(_p.velx) + Math.abs(_p.vely) < 0.5 && (Math.abs(diffX) < 1 || Math.abs(diffY) < 1 || iAmLeader)) { |
|
_p.tracer = { |
|
x: tracer.x, |
|
y: tracer.y |
|
}; |
|
_p.velx = Math.random() * jump - (jump/2); |
|
_p.vely = Math.random() * jump - (jump/2); |
|
} |
|
|
|
if (diffX && diffY) { |
|
_p.velx += diffX / 1000; |
|
_p.vely += diffY / 1000; |
|
} |
|
|
|
// friction |
|
_p.velx *= friction; |
|
_p.vely *= friction; |
|
|
|
// We don't want to let the particles leave the |
|
// area, so just change their position when they |
|
// touch the walls of the window |
|
if(_p.x >= winWidth && _p.velx > 0 || _p.x < 0 && _p.velx < 0){ _p.velx *= -1; } |
|
if(_p.y >= winHeight && _p.vely > 0 || _p.y < 0 && _p.vely < 0){ _p.vely *= -1; } |
|
|
|
// tick adjust |
|
_p.x += _p.velx * tick; |
|
_p.y += _p.vely * tick; |
|
|
|
_vel = Math.abs(_p.velx) + Math.abs(_p.vely); |
|
_p.fillStyle = Math.min(Math.round((_vel/10) * 99), 99); |
|
}; |
|
|
|
function setTracer(e) { |
|
e = e || window.event; |
|
tracer.x = e.pageX; |
|
tracer.y = e.pageY; |
|
|
|
lastMouseMove = (new Date()).getTime(); |
|
} |
|
|
|
// ------------------------------ // |
|
// #### Draw the canvas data #### // |
|
// ------------------------------ // |
|
function drawScene(){ |
|
var i, len, f, _p; |
|
|
|
ctx.clearRect(0, 0, winWidth, winHeight); |
|
_p = particles[i-1]; |
|
for (f = fillStyles.length; f--;) { |
|
// draw each particle |
|
len = particles.length; |
|
ctx.fillStyle = fillStyles[f]; |
|
|
|
for(i = 0; i < len ; i++){ |
|
_p = particles[i]; |
|
if (_p.fillStyle !== f) { continue; } |
|
_p.draw(); |
|
} |
|
} |
|
|
|
// draw the FPS clock |
|
ctx.lineWidth=1; |
|
ctx.fillStyle="#CCCCCC"; |
|
ctx.font="16px sans-serif"; |
|
ctx.fillText("FPS: " + Math.round(avgFPS), 20, 30); |
|
ctx.fillText("Particles: " + particles.length, 20, 50); |
|
|
|
// update the scene |
|
update(); |
|
|
|
// schedule the next update |
|
rAF(drawScene); |
|
} |
|
|
|
// ------------------------------------------------ // |
|
// #### Update the scene, animate all elements #### // |
|
// ------------------------------------------------ // |
|
function update(){ |
|
|
|
// 1000ms is 1s, divided by the number of ms it took |
|
// us since last update gives us how many times we |
|
// could have performed this frame within 1 second (FPS) |
|
var _timer = (new Date()).getTime(); |
|
var _lastFPS = FPS; |
|
var _i; |
|
|
|
FPS = 1000 / (_timer - timer); |
|
frameBuffer.shift(); |
|
frameBuffer.push(FPS); |
|
// average the new FPS value with the last one to ease the change rate |
|
avgFPS = frameBuffer.reduce(function (prev, cur) { return prev + cur; }, 0) / frameBuffer.length; |
|
timer = _timer; |
|
|
|
// tick is how far from the ideal speed we are |
|
// if we are rendering slower, this number will be higher. |
|
// we'll use this to adjust the amount of movement we perform |
|
// on each particle per frame. |
|
tick = targetFPS / FPS; |
|
|
|
if (avgFPS > 55) { |
|
particles.push(new Particle()); |
|
particles.push(new Particle()); |
|
} else if (avgFPS < 45) { |
|
particles.pop(); |
|
particles.pop(); |
|
} |
|
|
|
// look at each particle and evaluate how it should move |
|
// next based on it's current valocity and attraction to |
|
// any other particles that are close enough. |
|
for(_i = particles.length; _i-- ;){ |
|
particles[_i].update(tick); |
|
} |
|
} |
|
|
|
// -------------------------------------------- // |
|
// #### Initialize and setup the animation #### // |
|
// -------------------------------------------- // |
|
function init(){ |
|
var i; |
|
|
|
initializing = true; |
|
// Get a reference to our canvas |
|
canvas = document.getElementById("canvas") || document.createElement("canvas"); |
|
canvas.id = 'canvas'; |
|
document.body.appendChild(canvas); |
|
sizeCanvas(); |
|
ctx = canvas.getContext("2d"); |
|
|
|
// setup init values |
|
particles = []; |
|
startCount = 100; |
|
|
|
for (i = 100; i--;) { |
|
fillStyles[i] = 'hsla(' + |
|
(200 * (i/99) + 200) + ', ' + |
|
'100%, 60%, ' + |
|
((i + 1) / 70 + 0.3) + |
|
')'; |
|
} |
|
|
|
// create all of our particles and push them into our main array to hold. |
|
i = startCount; |
|
for(; i-- ;){ |
|
particles[i] = new Particle(); |
|
} |
|
|
|
tracer = { |
|
x: winWidth/2, |
|
y: winHeight/2 |
|
}; |
|
|
|
if(!initialized){ |
|
drawScene(); |
|
bindEvents(); |
|
initialized = true; |
|
} |
|
initializing = false; |
|
} |
|
|
|
|
|
function sizeCanvas(){ |
|
winHeight = window.innerHeight; |
|
winWidth = window.innerWidth; |
|
|
|
canvas.width = winWidth; |
|
canvas.height = winHeight; |
|
canvas.style.width = winWidth + 'px'; |
|
canvas.style.height = winHeight + 'px'; |
|
} |
|
|
|
|
|
// -------------------------------------------------- // |
|
// #### Set up the event bindings for user input #### // |
|
// -------------------------------------------------- // |
|
function bindEvents(){ |
|
|
|
// resize canvas when window resizes |
|
window.addEventListener('resize', init, false); |
|
window.addEventListener('mousemove', setTracer, false); |
|
} |
|
|
|
// Initialize the animation |
|
init(); |
|
|
|
})(); |