The idea of this experiment is to create a grid of particles and then add a marker that the particles swarm to. When the marker disapears, the particles should return to their normal position.
A Pen by Gary Constable on CodePen.
The idea of this experiment is to create a grid of particles and then add a marker that the particles swarm to. When the marker disapears, the particles should return to their normal position.
A Pen by Gary Constable on CodePen.
| <!-- | |
| A work in progree, I want to be able to add a cirle (click on canvas!) | |
| that all the particles gravitate to. | |
| @author gary <gary.constable80@gmail.com> | |
| https://github.com/garyconstable/cloaked-octo-hipster | |
| --> |
| //utils and calcullations | |
| var utils = { | |
| lineLength : function(x, y, x0, y0){ | |
| return Math.sqrt((x -= x0) * x + (y -= y0) * y); | |
| } | |
| } | |
| //particle | |
| var particle = function(opts){ | |
| this.width = opts.width || 1; | |
| this.height = opts.height || 1; | |
| this.x_pos = opts.x_pos || 0; | |
| this.y_pos = opts.y_pos || 0; | |
| this.x_speed = opts.x_speed || Math.random()*10; | |
| this.y_speed = opts.y_speed || Math.random()*10; | |
| this.color = opts.color || 'rgb(255,255,255)'; | |
| this.canvas_width = opts.canvas_width || 800; | |
| this.canvas_height = opts.canvas_width || 500; | |
| this.draw = opts.draw || 'dot'; | |
| this.birth_state = this; | |
| } | |
| //the old move fuctions | |
| particle.prototype.animate = function(){ | |
| W=800;H=500; | |
| p = this; | |
| p.y_pos += p.y_speed; | |
| if(p.y_pos < -50) p.y_pos = H+50; | |
| if(p.y_pos > H+50) p.y_pos = -50; | |
| } | |
| //move up and down within the mask / bounds area | |
| particle.prototype.animate = function(){ | |
| p = this; | |
| p.y_pos += p.y_speed; | |
| } | |
| //revese the speed (and so the direction) of the particle | |
| particle.prototype.reverseSpeed = function(){ | |
| //make negative | |
| if(this.y_speed > 0){ | |
| //(20) * (-1) | |
| this.y_speed = (this.y_speed) * (-1); | |
| }else{ | |
| //(-20) * (-1) | |
| this.y_speed = (this.y_speed) * (-1); | |
| } | |
| } | |
| //https://raw2.github.com/garyconstable/cloaked-octo-hipster/develop/anchor.js | |
| var anchor = function(opts){ | |
| this.radius = opts.radius || 0; | |
| this.x_pos = opts.x_pos || 0; | |
| this.y_pos = opts.y_pos || 0; | |
| this.draw = opts.draw || 'circle'; | |
| this.lifespan = opts.draw || 2000; | |
| this.created = new Date().getTime(); | |
| this.canvas_width = opts.canvas_width || 800; | |
| this.canvas_height = opts.canvas_width || 500; | |
| this.animate = function(){} | |
| this.birth = this; | |
| } | |
| //particle factory - were already thing expansions.... | |
| var particleFactory = function(){}; | |
| particleFactory.prototype.create = function ( options, type ) { | |
| switch(type){ | |
| case 'anchor' : return new anchor( options ); | |
| default: return new particle( options ); | |
| } | |
| }; | |
| //canvas | |
| var Canvas = function(options){ | |
| var self = this; | |
| 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 canvar | |
| Canvas.prototype.clear = function(){ | |
| ctx.clearRect ( 0 , 0 , this.width , this.height ); | |
| } | |
| //pass to correct drawing function | |
| Canvas.prototype.render = function(opts){ | |
| switch(opts.draw){ | |
| case 'dot' : this.dot(opts); break; | |
| case 'circle' : this.circle(opts); break; | |
| } | |
| } | |
| //draw point | |
| Canvas.prototype.dot = function( opts ){ | |
| ctx.fillStyle = opts.color || 'rgb(255,255,255)'; | |
| ctx.fillRect(opts.x_pos,opts.y_pos,opts.width,opts.height); | |
| } | |
| //draw circle | |
| Canvas.prototype.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(); | |
| } | |
| Canvas.prototype.createBounds = function( opts ){ | |
| ctx = this.ctx; | |
| // this is the unicode hex of a heart | |
| //str = "2764"; | |
| str = "Hello...!"; | |
| fontStr = "150pt Helvetica Arial, sans-serif"; | |
| ctx.beginPath(); | |
| ctx.font = fontStr; | |
| ctx.textAlign = "center"; | |
| ctx.fillStyle = "#ffffff"; | |
| //ctx.fillText(String.fromCharCode(parseInt(str, 16)),this.width/2 ,this.height - 50); | |
| ctx.fillText( str, this.width/2 ,this.height/2+50); | |
| 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; | |
| } | |
| Canvas.prototype.toPosX = function(i,w) { | |
| return (i % (4 * w)) / 4; | |
| } | |
| Canvas.prototype.toPosY = function(i, w){ | |
| return Math.floor(i / (4 * w)); | |
| } | |
| //get the color of the specifed pixel | |
| Canvas.prototype.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]; | |
| } | |
| Canvas.prototype.addToMask = function(obj){ | |
| var tc = Canvas; | |
| console.log(tc); | |
| var c = document.createElement('canvas'); | |
| c.id = "tc"; | |
| c.width = tc.width; | |
| c.height = tc.height; | |
| //this.canvas.bound.push(black pixel) | |
| } | |
| Canvas.prototype.removeFromMask = function(){ | |
| //this.canvas.bound.remove(black pixel) | |
| } | |
| // i think it would be better to keep this seperate | |
| var particleSystem = function(options){ | |
| this.total_particles = 10000; | |
| this.particles_drawn = 0; | |
| this.particles = [ ]; | |
| this.nodes = [ ]; | |
| this.nodes_pointer = [ ]; | |
| this.canvas = options.canvas; | |
| this.createParticles(); | |
| this.self = this; | |
| } | |
| //create the particles | |
| particleSystem.prototype.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+=30){ | |
| this.particles.push( pf.create({ | |
| x_pos: this.canvas.bounds[i][0], | |
| y_pos: this.canvas.bounds[i][1], | |
| color: 'rgb(0,92,92)', | |
| width: 3, | |
| height: 3, | |
| y_speed: Math.floor(Math.random() * 5) + 1 | |
| }) ); | |
| this.particles_drawn++; | |
| } | |
| } | |
| //add a node to the canvas | |
| particleSystem.prototype.addNode = function(event){ | |
| //create an anchor via the factory | |
| var obj = new particleFactory().create({ | |
| x_pos : event.layerX, | |
| y_pos : event.layerY, | |
| }, 'anchor'); | |
| //to be drawn it muct be part of the particle system | |
| this.particles.push( obj ); | |
| // we need a ref to the array | |
| this.nodes_pointer.push(obj.created); | |
| this.nodes.push(obj); | |
| // just for my info | |
| this.particles_drawn++; | |
| //add the new mask pixels? | |
| this.canvas.addToMask(obj, this.canvas); | |
| } | |
| //remove element from particle system array | |
| particleSystem.prototype.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 | |
| particleSystem.prototype.render = function(self){ | |
| var i=0; | |
| this.canvas.clear(); | |
| this.particles.forEach( function(particle) { | |
| if( self.remove(particle, i) === false ){ | |
| //change the particle in some way | |
| if (typeof particle.animate !== "undefined") { | |
| //get the pixel color | |
| //this bit is still tooo slowwww | |
| var pc = self.canvas.pixelColor(particle.x_pos, particle.y_pos); | |
| //if its a black pixel | |
| if(pc[0] == 0 && pc[1] == 0 && pc[2] == 0 && pc[3] == 0){ | |
| try{ | |
| particle.reverseSpeed(); | |
| }catch(ex){} | |
| //contiune in the same direction.. | |
| } | |
| //move the particle | |
| particle.animate(); | |
| //do something with the node and current particle. | |
| if(self.nodes.length !== 0){ | |
| for(x=0; x<self.nodes.length;x++){ | |
| var d = self.nodes[x]; | |
| var e = utils.lineLength(d.x_pos,d.y_pos,particle.x_pos,particle.y_pos); | |
| if( e < 25 ){ //its close | |
| particle.color = 'rgb(255,0,0)'; //red | |
| } | |
| } | |
| }else{ | |
| try{ | |
| particle.color = 'rgb(0,92,92)'; //matrix green | |
| }catch(ex){} | |
| } | |
| } | |
| //render the particle | |
| Canvas.render(particle); | |
| }//eo remove | |
| i++; | |
| }); | |
| } | |
| //remove element from particles array | |
| particleSystem.prototype.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); | |
| //console.log(self.nodes); | |
| } | |
| return true; | |
| } | |
| //app | |
| var App = function(options){ | |
| //create a canvas and add to body | |
| var c = document.createElement('canvas'); | |
| c.id = "canvas"; | |
| c.width = 800; | |
| c.height = 400; | |
| 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' }); | |
| Canvas.surface.addEventListener('click', function(event) { self.ps.addNode(event);}, false); | |
| //create the shape | |
| Canvas.createBounds(); | |
| //create the particle system obj | |
| this.ps = new particleSystem({'canvas' : Canvas}) | |
| self = this; | |
| } | |
| //render loop | |
| App.prototype.run = function(){ | |
| self.ps.render(self.ps); | |
| requestAnimationFrame( self.run ); | |
| } | |
| //animation frame | |
| window.requestAnimFrame = (function(){ | |
| return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || | |
| function( callback ){ | |
| window.setTimeout(callback, 1000 / 60); | |
| }; | |
| })(); | |
| //init + animate + the go button.. | |
| var app = new App().run(); |