Skip to content

Instantly share code, notes, and snippets.

@garyconstable
Created May 23, 2014 23:02
Show Gist options
  • Save garyconstable/8048f2f7f156a7b0cc99 to your computer and use it in GitHub Desktop.
Save garyconstable/8048f2f7f156a7b0cc99 to your computer and use it in GitHub Desktop.
A Pen by Gary Constable.
<style>
canvas{
padding: 0;
margin:15% auto 0 auto;
display: block;
}
</style>
/**
* 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