A Pen by Gary Constable on CodePen.
Created
May 23, 2014 23:02
-
-
Save garyconstable/8048f2f7f156a7b0cc99 to your computer and use it in GitHub Desktop.
A Pen by Gary Constable.
This file contains 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
<style> | |
canvas{ | |
padding: 0; | |
margin:15% auto 0 auto; | |
display: block; | |
} | |
</style> |
This file contains 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
/** | |
* Hello particles v1.1 | |
* garyconstable.co.uk/particles/ | |
* @author gary <garyconstable80gmail.com> | |
*/ | |
/** | |
* config | |
* @type Number | |
*/ | |
var config = { | |
//particles alowed in the mask i x numParticles | |
numParticles: 75, | |
//line | |
lineDistance: 0, | |
lookAhead: 1, | |
//particle | |
particleWidth: 2, | |
particleHeight: 2, | |
particleSpeed: 1, | |
//canvas | |
canvasWidth: 800, | |
canvasHeight: 400, | |
//allow animations | |
canAnimate: false, | |
//what to draw | |
boundsType: 0, | |
//msg | |
msg: "welcome", | |
//unicode | |
unicode: "2B24", | |
//font-size | |
fontsize: '160pt' | |
}; | |
/** | |
* after pause start the animations | |
* @returns {undefined} | |
*/ | |
setTimeout(function(){ | |
config.canAnimate = true; | |
config.lineDistance = 20; | |
}, 0); | |
/** | |
* utilis | |
* @type type | |
*/ | |
var utils = { | |
lineLength : function(x, y, x0, y0){ | |
return Math.sqrt((x -= x0) * x + (y -= y0) * y); | |
} | |
} | |
/** | |
* particle definition | |
* @param {type} opts | |
* @returns {particle} | |
*/ | |
var particle = function( opts ){ | |
this.width = opts.width || 1; | |
this.height = opts.height || 1; | |
this.x = opts.x || 0; | |
this.y = opts.y || 0; | |
this.angle = Math.floor(Math.random()*360); | |
this.speed = opts.speed || 5; | |
this.radius = 10; | |
this.color = opts.color || 'rgba(255, 0, 0, 0)'; | |
this.canvas_width = opts.canvas_width || 800; | |
this.canvas_height = opts.canvas_width || 400; | |
this.draw = opts.draw || 'dot'; | |
//change the x Direction | |
this.reverseX = function(){ | |
this.angle = 180 - this.angle; | |
} | |
//change the y Direction | |
this.reverseY = function(){ | |
this.angle = 360 - this.angle; | |
} | |
//update the particle - angle to radians | |
this.update = function updateBall() { | |
this.radians = this.angle * Math.PI/ 180; | |
this.x += Math.cos(this.radians) * this.speed; | |
this.y += Math.sin(this.radians) * this.speed; | |
} | |
//based on positions change angle | |
this.animate = function(){ | |
if ( this.x > this.canvas_width || this.x < 0 ) { | |
this.angle = 180 - this.angle; | |
} else if ( this.y > this.canvas_height || this.y < 0) { | |
this.angle = 360 - this.angle; | |
} | |
this.update(); | |
} | |
} | |
/** | |
* factory for creating particle / objects | |
* @returns {particleFactory} | |
*/ | |
var particleFactory = function(){ | |
this.create = function ( options, type ) { | |
switch(type){ | |
default: return new particle( options ); | |
} | |
} | |
} | |
/** | |
* custom drawing - method class for lines , circles etc.. | |
* @param {type} options | |
* @returns {Canvas} | |
*/ | |
var Canvas = function(options){ | |
this.surface = options.surface || "canvas"; | |
this.surface = document.getElementById(this.surface); | |
this.width = this.surface.offsetWidth; | |
this.height = this.surface.offsetHeight; | |
this.bounds; | |
this.non_bounds; | |
this.mask = []; | |
var ctx = this.surface.getContext('2d'); | |
this.ctx = ctx; | |
//clear the canvas | |
this.clear = function(){ | |
ctx.clearRect ( 0 , 0 , this.width , this.height ); | |
} | |
//pass to correct drawing function | |
this.render = function(opts){ | |
switch(opts.draw){ | |
case 'dot' : this.dot(opts); break; | |
case 'circle' : this.circle(opts); break; | |
case 'ball' : this.ball(opts); break; | |
case 'line' : this.line(opts); break; | |
} | |
} | |
//draw point | |
this.dot = function( opts ){ | |
ctx.fillStyle = opts.color || 'rgb(255,255,255)'; | |
ctx.fillRect(opts.x,opts.y,opts.width,opts.height); | |
} | |
//draw circle | |
this.circle = function( opts ){ | |
radius = opts.radius || 10; | |
color = opts.color || 'rgb(255,0,0)'; | |
ctx.beginPath(); | |
ctx.fillStyle = color; | |
ctx.arc(opts.x_pos, opts.y_pos, radius, 0, Math.PI*2, true); | |
ctx.closePath(); | |
ctx.fill(); | |
} | |
//draw a line between 2 points | |
this.line = function( opts ){ | |
color = opts.color || 'rgba(255,255,255,0.5)'; | |
p1 = opts.p1 || 0; | |
p2 = opts.p2 || 0; | |
p3 = opts.p3 || 0; | |
p4 = opts.p4 || 0; | |
ctx.beginPath(); | |
ctx.strokeStyle = color; | |
ctx.moveTo(p1 , p2 ); | |
ctx.lineTo(p3 , p4 ); | |
ctx.stroke(); | |
} | |
//gradient ball | |
this.ball = function(opts){ | |
ctx.beginPath(); | |
var gradient = ctx.createRadialGradient( | |
opts.x, | |
opts.y, | |
0, | |
opts.x, | |
opts.y, | |
opts.radius | |
); | |
gradient.addColorStop(0, "rgba(255,255,255,0.75)"); | |
gradient.addColorStop(0.4, "rgba(255,255,255,0.75)"); | |
gradient.addColorStop(0.4, opts.color); | |
ctx.fillStyle = gradient; | |
ctx.arc( | |
opts.x, | |
opts.y, | |
opts.radius, | |
0, | |
Math.PI*2,true | |
); | |
ctx.shadowColor = '#999'; | |
ctx.shadowBlur = 30; | |
ctx.shadowOffsetX = 10; | |
ctx.shadowOffsetY = 15; | |
ctx.closePath(); | |
ctx.fill(); | |
} | |
//create bounds | |
this.createBounds = function( opts ){ | |
ctx = this.ctx; | |
switch(config.boundsType){ | |
case 0: | |
str = config.msg; | |
fontStr = config.fontsize + " Helvetica Arial, sans-serif"; | |
break; | |
default: | |
//unicode number | |
str = config.unicode; | |
fontStr = config.fontsize + " Helvetica Arial, sans-serif"; | |
break; | |
} | |
ctx.beginPath(); | |
ctx.font = fontStr; | |
ctx.textAlign = "center"; | |
ctx.fillStyle = "#ffffff"; | |
switch(config.boundsType){ | |
case 0: | |
ctx.fillText( str, this.width/2 ,this.height/2+50); | |
break; | |
default: | |
ctx.fillText(String.fromCharCode(parseInt(str, 16)),this.width/2 ,this.height); | |
break; | |
} | |
ctx.closePath(); | |
this.mask = ctx.getImageData(0,0,this.width,this.height); | |
area = []; | |
non_area =[]; | |
// save all white pixels, these will be used as the bounds for the particles | |
//http://falcon80.com/HTMLCanvas/PixelManipulation/getImageData.html | |
//https://github.com/ottis/canvas-particles/blob/master/js/particle_system.js | |
for (var i = 0; i < this.mask.data.length; i+=4) { | |
if (this.mask.data[i] == 255 && this.mask.data[i+1] == 255 && this.mask.data[i+2] == 255 && this.mask.data[i+3] == 255) { | |
area.push([this.toPosX(i,this.mask.width),this.toPosY(i,this.mask.width)]); | |
}else{ | |
//non_area.push([this.toPosX(i,mask.width),this.toPosY(i,mask.width)]); | |
} | |
} | |
this.bounds = area; | |
this.non_bounds = non_area; | |
} | |
//ge the xpos | |
this.toPosX = function(i,w) { | |
return (i % (4 * w)) / 4; | |
} | |
//get theh y pos | |
this.toPosY = function(i, w){ | |
return Math.floor(i / (4 * w)); | |
} | |
//get the color of the specifed pixel | |
this.pixelColor = function(objectx, objecty){ | |
var imageData = this.mask | |
var inputData = imageData.data; | |
var pData = (~~objectx + (~~objecty * this.mask.width)) * 4; | |
var r = inputData[pData],g = inputData[pData + 1],b = inputData[pData + 2],a = inputData[pData + 3]; | |
return [r,g,b,a]; | |
} | |
//add to the mask | |
this.addToMask = function(obj){ | |
var tc = Canvas; | |
var c = document.createElement('canvas'); | |
c.id = "tc"; | |
c.width = tc.width; | |
c.height = tc.height; | |
} | |
} | |
/** | |
* | |
* @param {type} options | |
* @returns {particleSystem} | |
*/ | |
var particleSystem = function(options){ | |
this.total_particles = 10; | |
this.particles_drawn = 0; | |
this.particles = [ ]; | |
this.nodes = [ ]; | |
this.nodes_pointer = [ ]; | |
this.canvas = options.canvas; | |
//using our the factory and template make some particles | |
this.createParticles = function(){ | |
pf = new particleFactory(); | |
w = this.canvas.width; | |
h = this.canvas.height; | |
x = 0; | |
y = 0; | |
// create the particles, add base 2 number, higher = less particles | |
for(var i=0; i<this.canvas.bounds.length;i+=config.numParticles){ | |
this.particles.push( pf.create({ | |
x: this.canvas.bounds[i][0], | |
y: this.canvas.bounds[i][1], | |
color: 'rgb(0,92,92)', | |
width: config.particleWidth, | |
height: config.particleHeight, | |
speed: config.particleSpeed || Math.floor(Math.random() * 5) + 1 | |
}) ); | |
this.particles_drawn++; | |
} | |
} | |
//remove element from particle system array | |
this.remove = function(particle, index){ | |
if (typeof particle.lifespan != "undefined") { | |
if( (new Date().getTime() - particle.created) > particle.lifespan){ | |
return this.crop(index, particle.created); | |
} | |
} | |
return false; | |
} | |
//render loop clear canvas and redraw | |
this.render = function(self){ | |
var i=0; | |
var particles = this.particles; | |
this.canvas.clear(); | |
this.particles.forEach( function(particle) { | |
if( self.remove(particle, i) === false ){ | |
var canAnimate = true; | |
//change the particle in some way | |
if (typeof particle.animate !== "undefined") { | |
//get the pixel color | |
var pc = self.canvas.pixelColor(particle.x, particle.y); | |
//if its a black pixel | |
if(pc[0] == 0 && pc[1] == 0 && pc[2] == 0 && pc[3] == 0){ | |
try{ | |
//--> note to self i think here we need to look ahead at the next pixel for x and y | |
//--> if x is black reverse x | |
var xCol = self.canvas.pixelColor( (particle.x+1), particle.y); | |
var yCol = self.canvas.pixelColor( (particle.x), particle.y+1); | |
if(xCol[0] == 0 && xCol[1] == 0 && xCol[2] == 0 && xCol[3] == 0){ | |
particle.reverseX(); | |
canAnimate = false; | |
} | |
if(yCol[0] == 0 && yCol[1] == 0 && yCol[2] == 0 && yCol[3] == 0){ | |
particle.reverseY(); | |
canAnimate = false; | |
} | |
}catch(ex){ | |
//--> throw an error | |
} | |
} | |
//move the particle - after the timeout | |
if( config.canAnimate === true){ | |
particle.animate() | |
} | |
//connecting line | |
var distance = config.lineDistance; | |
for(var j=0; j<particles.length; j++){ | |
//draw the line | |
if( utils.lineLength(particles[i]['x'], particles[i]['y'], particles[j]['x'], particles[j]['y']) <= distance ){ | |
var opts = { | |
draw: 'line', | |
p1: particles[i]['x'], | |
p2: particles[i]['y'], | |
p3: particles[j]['x'], | |
p4: particles[j]['y'] | |
}; | |
Canvas.render(opts); | |
} | |
} | |
} | |
//render the particle | |
Canvas.render(particle); | |
}//eo remove | |
i++; | |
}); | |
} | |
//remove element from particles array | |
this.crop = function(index, ts){ | |
//remove from the drawing array | |
this.particles.splice(index, 1); | |
this.particles_drawn--; | |
//remove the node pointers to the node | |
var i = this.nodes_pointer.indexOf(ts); | |
if(i !== -1){ | |
this.nodes.splice(i, 1); | |
this.nodes_pointer.splice(i, 1); | |
} | |
return true; | |
} | |
this.createParticles(); | |
this.self = this; | |
} | |
/** | |
* | |
* @param {type} options | |
* @returns {App} | |
*/ | |
var App = function(options){ | |
//create a canvas and add to body | |
var c = document.createElement('canvas'); | |
c.id = "canvas"; | |
c.width = config.canvasWidth; | |
c.height = config.canvasHeight; | |
//c.style.border = "1px solid white"; | |
document.getElementsByTagName("body")[0].style.background = 'black'; | |
document.getElementsByTagName("body")[0].appendChild(c); | |
//create canvas obj and mouselistener | |
Canvas = new Canvas({ 'surface' : 'canvas' }); | |
//create the shape | |
Canvas.createBounds(); | |
//create the particle system obj | |
this.ps = new particleSystem({'canvas' : Canvas}) | |
self = this; | |
this.run = function(){ | |
self.ps.render(self.ps); | |
requestAnimationFrame( self.run ); | |
} | |
} | |
/** | |
* | |
* @type @exp;window@pro;webkitRequestAnimationFrame|@exp;window@pro;mozRequestAnimationFrame|@exp;window@pro;requestAnimationFrame | |
*/ | |
window.requestAnimFrame = (function(){ | |
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || | |
function( callback ){ | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
})(); | |
/** | |
* | |
* @type @new;App@call;run | |
*/ | |
var app = new App().run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment