/* ----------------------------------------------- /* author : vincent garreau - vincentgarreau.com /* mit license: http://opensource.org/licenses/mit /* demo / generator : vincentgarreau.com/particles.js /* github : github.com/vincentgarreau/particles.js /* how to use? : check the github readme /* v2.0.0 /* ----------------------------------------------- */ var pjs = function(tag_id, params){ var canvas_el = document.queryselector('#'+tag_id+' > .particles-js-canvas-el'); /* particles.js variables with default values */ this.pjs = { canvas: { el: canvas_el, w: canvas_el.offsetwidth, h: canvas_el.offsetheight }, particles: { number: { value: 400, density: { enable: true, value_area: 800 } }, color: { value: '#fff' }, shape: { type: 'circle', stroke: { width: 0, color: '#ff0000' }, polygon: { nb_sides: 5 }, image: { src: '', width: 100, height: 100 } }, opacity: { value: 1, random: false, anim: { enable: false, speed: 2, opacity_min: 0, sync: false } }, size: { value: 20, random: false, anim: { enable: false, speed: 20, size_min: 0, sync: false } }, line_linked: { enable: true, distance: 100, color: '#fff', opacity: 1, width: 1 }, move: { enable: true, speed: 2, direction: 'none', random: false, straight: false, out_mode: 'out', bounce: false, attract: { enable: false, rotatex: 3000, rotatey: 3000 } }, array: [] }, interactivity: { detect_on: 'canvas', events: { onhover: { enable: true, mode: 'grab' }, onclick: { enable: true, mode: 'push' }, resize: true }, modes: { grab:{ distance: 100, line_linked:{ opacity: 1 } }, bubble:{ distance: 200, size: 80, duration: 0.4 }, repulse:{ distance: 200, duration: 0.4 }, push:{ particles_nb: 4 }, remove:{ particles_nb: 2 } }, mouse:{} }, retina_detect: false, fn: { interact: {}, modes: {}, vendors:{} }, tmp: {} }; var pjs = this.pjs; /* params settings */ if(params){ object.deepextend(pjs, params); } pjs.tmp.obj = { size_value: pjs.particles.size.value, size_anim_speed: pjs.particles.size.anim.speed, move_speed: pjs.particles.move.speed, line_linked_distance: pjs.particles.line_linked.distance, line_linked_width: pjs.particles.line_linked.width, mode_grab_distance: pjs.interactivity.modes.grab.distance, mode_bubble_distance: pjs.interactivity.modes.bubble.distance, mode_bubble_size: pjs.interactivity.modes.bubble.size, mode_repulse_distance: pjs.interactivity.modes.repulse.distance }; pjs.fn.retinainit = function(){ if(pjs.retina_detect && window.devicepixelratio > 1){ pjs.canvas.pxratio = window.devicepixelratio; pjs.tmp.retina = true; } else{ pjs.canvas.pxratio = 1; pjs.tmp.retina = false; } pjs.canvas.w = pjs.canvas.el.offsetwidth * pjs.canvas.pxratio; pjs.canvas.h = pjs.canvas.el.offsetheight * pjs.canvas.pxratio; pjs.particles.size.value = pjs.tmp.obj.size_value * pjs.canvas.pxratio; pjs.particles.size.anim.speed = pjs.tmp.obj.size_anim_speed * pjs.canvas.pxratio; pjs.particles.move.speed = pjs.tmp.obj.move_speed * pjs.canvas.pxratio; pjs.particles.line_linked.distance = pjs.tmp.obj.line_linked_distance * pjs.canvas.pxratio; pjs.interactivity.modes.grab.distance = pjs.tmp.obj.mode_grab_distance * pjs.canvas.pxratio; pjs.interactivity.modes.bubble.distance = pjs.tmp.obj.mode_bubble_distance * pjs.canvas.pxratio; pjs.particles.line_linked.width = pjs.tmp.obj.line_linked_width * pjs.canvas.pxratio; pjs.interactivity.modes.bubble.size = pjs.tmp.obj.mode_bubble_size * pjs.canvas.pxratio; pjs.interactivity.modes.repulse.distance = pjs.tmp.obj.mode_repulse_distance * pjs.canvas.pxratio; }; /* ---------- pjs functions - canvas ------------ */ pjs.fn.canvasinit = function(){ pjs.canvas.ctx = pjs.canvas.el.getcontext('2d'); }; pjs.fn.canvassize = function(){ pjs.canvas.el.width = pjs.canvas.w; pjs.canvas.el.height = pjs.canvas.h; if(pjs && pjs.interactivity.events.resize){ window.addeventlistener('resize', function(){ pjs.canvas.w = pjs.canvas.el.offsetwidth; pjs.canvas.h = pjs.canvas.el.offsetheight; /* resize canvas */ if(pjs.tmp.retina){ pjs.canvas.w *= pjs.canvas.pxratio; pjs.canvas.h *= pjs.canvas.pxratio; } pjs.canvas.el.width = pjs.canvas.w; pjs.canvas.el.height = pjs.canvas.h; /* repaint canvas on anim disabled */ if(!pjs.particles.move.enable){ pjs.fn.particlesempty(); pjs.fn.particlescreate(); pjs.fn.particlesdraw(); pjs.fn.vendors.densityautoparticles(); } /* density particles enabled */ pjs.fn.vendors.densityautoparticles(); }); } }; pjs.fn.canvaspaint = function(){ pjs.canvas.ctx.fillrect(0, 0, pjs.canvas.w, pjs.canvas.h); }; pjs.fn.canvasclear = function(){ pjs.canvas.ctx.clearrect(0, 0, pjs.canvas.w, pjs.canvas.h); }; /* --------- pjs functions - particles ----------- */ pjs.fn.particle = function(color, opacity, position){ /* size */ this.radius = (pjs.particles.size.random ? math.random() : 1) * pjs.particles.size.value; if(pjs.particles.size.anim.enable){ this.size_status = false; this.vs = pjs.particles.size.anim.speed / 100; if(!pjs.particles.size.anim.sync){ this.vs = this.vs * math.random(); } } /* position */ this.x = position ? position.x : math.random() * pjs.canvas.w; this.y = position ? position.y : math.random() * pjs.canvas.h; /* check position - into the canvas */ if(this.x > pjs.canvas.w - this.radius*2) this.x = this.x - this.radius; else if(this.x < this.radius*2) this.x = this.x + this.radius; if(this.y > pjs.canvas.h - this.radius*2) this.y = this.y - this.radius; else if(this.y < this.radius*2) this.y = this.y + this.radius; /* check position - avoid overlap */ if(pjs.particles.move.bounce){ pjs.fn.vendors.checkoverlap(this, position); } /* color */ this.color = {}; if(typeof(color.value) == 'object'){ if(color.value instanceof array){ var color_selected = color.value[math.floor(math.random() * pjs.particles.color.value.length)]; this.color.rgb = hextorgb(color_selected); }else{ if(color.value.r != undefined && color.value.g != undefined && color.value.b != undefined){ this.color.rgb = { r: color.value.r, g: color.value.g, b: color.value.b } } if(color.value.h != undefined && color.value.s != undefined && color.value.l != undefined){ this.color.hsl = { h: color.value.h, s: color.value.s, l: color.value.l } } } } else if(color.value == 'random'){ this.color.rgb = { r: (math.floor(math.random() * (255 - 0 + 1)) + 0), g: (math.floor(math.random() * (255 - 0 + 1)) + 0), b: (math.floor(math.random() * (255 - 0 + 1)) + 0) } } else if(typeof(color.value) == 'string'){ this.color = color; this.color.rgb = hextorgb(this.color.value); } /* opacity */ this.opacity = (pjs.particles.opacity.random ? math.random() : 1) * pjs.particles.opacity.value; if(pjs.particles.opacity.anim.enable){ this.opacity_status = false; this.vo = pjs.particles.opacity.anim.speed / 100; if(!pjs.particles.opacity.anim.sync){ this.vo = this.vo * math.random(); } } /* animation - velocity for speed */ var velbase = {} switch(pjs.particles.move.direction){ case 'top': velbase = { x:0, y:-1 }; break; case 'top-right': velbase = { x:0.5, y:-0.5 }; break; case 'right': velbase = { x:1, y:-0 }; break; case 'bottom-right': velbase = { x:0.5, y:0.5 }; break; case 'bottom': velbase = { x:0, y:1 }; break; case 'bottom-left': velbase = { x:-0.5, y:1 }; break; case 'left': velbase = { x:-1, y:0 }; break; case 'top-left': velbase = { x:-0.5, y:-0.5 }; break; default: velbase = { x:0, y:0 }; break; } if(pjs.particles.move.straight){ this.vx = velbase.x; this.vy = velbase.y; if(pjs.particles.move.random){ this.vx = this.vx * (math.random()); this.vy = this.vy * (math.random()); } }else{ this.vx = velbase.x + math.random()-0.5; this.vy = velbase.y + math.random()-0.5; } // var theta = 2.0 * math.pi * math.random(); // this.vx = math.cos(theta); // this.vy = math.sin(theta); this.vx_i = this.vx; this.vy_i = this.vy; /* if shape is image */ var shape_type = pjs.particles.shape.type; if(typeof(shape_type) == 'object'){ if(shape_type instanceof array){ var shape_selected = shape_type[math.floor(math.random() * shape_type.length)]; this.shape = shape_selected; } }else{ this.shape = shape_type; } if(this.shape == 'image'){ var sh = pjs.particles.shape; this.img = { src: sh.image.src, ratio: sh.image.width / sh.image.height } if(!this.img.ratio) this.img.ratio = 1; if(pjs.tmp.img_type == 'svg' && pjs.tmp.source_svg != undefined){ pjs.fn.vendors.createsvgimg(this); if(pjs.tmp.pushing){ this.img.loaded = false; } } } }; pjs.fn.particle.prototype.draw = function() { var p = this; if(p.radius_bubble != undefined){ var radius = p.radius_bubble; }else{ var radius = p.radius; } if(p.opacity_bubble != undefined){ var opacity = p.opacity_bubble; }else{ var opacity = p.opacity; } if(p.color.rgb){ var color_value = 'rgba('+p.color.rgb.r+','+p.color.rgb.g+','+p.color.rgb.b+','+opacity+')'; }else{ var color_value = 'hsla('+p.color.hsl.h+','+p.color.hsl.s+'%,'+p.color.hsl.l+'%,'+opacity+')'; } pjs.canvas.ctx.fillstyle = color_value; pjs.canvas.ctx.beginpath(); switch(p.shape){ case 'circle': pjs.canvas.ctx.arc(p.x, p.y, radius, 0, math.pi * 2, false); break; case 'edge': pjs.canvas.ctx.rect(p.x-radius, p.y-radius, radius*2, radius*2); break; case 'triangle': pjs.fn.vendors.drawshape(pjs.canvas.ctx, p.x-radius, p.y+radius / 1.66, radius*2, 3, 2); break; case 'polygon': pjs.fn.vendors.drawshape( pjs.canvas.ctx, p.x - radius / (pjs.particles.shape.polygon.nb_sides/3.5), // startx p.y - radius / (2.66/3.5), // starty radius*2.66 / (pjs.particles.shape.polygon.nb_sides/3), // sidelength pjs.particles.shape.polygon.nb_sides, // sidecountnumerator 1 // sidecountdenominator ); break; case 'star': pjs.fn.vendors.drawshape( pjs.canvas.ctx, p.x - radius*2 / (pjs.particles.shape.polygon.nb_sides/4), // startx p.y - radius / (2*2.66/3.5), // starty radius*2*2.66 / (pjs.particles.shape.polygon.nb_sides/3), // sidelength pjs.particles.shape.polygon.nb_sides, // sidecountnumerator 2 // sidecountdenominator ); break; case 'image': function draw(){ pjs.canvas.ctx.drawimage( img_obj, p.x-radius, p.y-radius, radius*2, radius*2 / p.img.ratio ); } if(pjs.tmp.img_type == 'svg'){ var img_obj = p.img.obj; }else{ var img_obj = pjs.tmp.img_obj; } if(img_obj){ draw(); } break; } pjs.canvas.ctx.closepath(); if(pjs.particles.shape.stroke.width > 0){ pjs.canvas.ctx.strokestyle = pjs.particles.shape.stroke.color; pjs.canvas.ctx.linewidth = pjs.particles.shape.stroke.width; pjs.canvas.ctx.stroke(); } pjs.canvas.ctx.fill(); }; pjs.fn.particlescreate = function(){ for(var i = 0; i < pjs.particles.number.value; i++) { pjs.particles.array.push(new pjs.fn.particle(pjs.particles.color, pjs.particles.opacity.value)); } }; pjs.fn.particlesupdate = function(){ for(var i = 0; i < pjs.particles.array.length; i++){ /* the particle */ var p = pjs.particles.array[i]; // var d = ( dx = pjs.interactivity.mouse.click_pos_x - p.x ) * dx + ( dy = pjs.interactivity.mouse.click_pos_y - p.y ) * dy; // var f = -bang_size / d; // if ( d < bang_size ) { // var t = math.atan2( dy, dx ); // p.vx = f * math.cos(t); // p.vy = f * math.sin(t); // } /* move the particle */ if(pjs.particles.move.enable){ var ms = pjs.particles.move.speed/2; p.x += p.vx * ms; p.y += p.vy * ms; } /* change opacity status */ if(pjs.particles.opacity.anim.enable) { if(p.opacity_status == true) { if(p.opacity >= pjs.particles.opacity.value) p.opacity_status = false; p.opacity += p.vo; }else { if(p.opacity <= pjs.particles.opacity.anim.opacity_min) p.opacity_status = true; p.opacity -= p.vo; } if(p.opacity < 0) p.opacity = 0; } /* change size */ if(pjs.particles.size.anim.enable){ if(p.size_status == true){ if(p.radius >= pjs.particles.size.value) p.size_status = false; p.radius += p.vs; }else{ if(p.radius <= pjs.particles.size.anim.size_min) p.size_status = true; p.radius -= p.vs; } if(p.radius < 0) p.radius = 0; } /* change particle position if it is out of canvas */ if(pjs.particles.move.out_mode == 'bounce'){ var new_pos = { x_left: p.radius, x_right: pjs.canvas.w, y_top: p.radius, y_bottom: pjs.canvas.h } }else{ var new_pos = { x_left: -p.radius, x_right: pjs.canvas.w + p.radius, y_top: -p.radius, y_bottom: pjs.canvas.h + p.radius } } if(p.x - p.radius > pjs.canvas.w){ p.x = new_pos.x_left; p.y = math.random() * pjs.canvas.h; } else if(p.x + p.radius < 0){ p.x = new_pos.x_right; p.y = math.random() * pjs.canvas.h; } if(p.y - p.radius > pjs.canvas.h){ p.y = new_pos.y_top; p.x = math.random() * pjs.canvas.w; } else if(p.y + p.radius < 0){ p.y = new_pos.y_bottom; p.x = math.random() * pjs.canvas.w; } /* out of canvas modes */ switch(pjs.particles.move.out_mode){ case 'bounce': if (p.x + p.radius > pjs.canvas.w) p.vx = -p.vx; else if (p.x - p.radius < 0) p.vx = -p.vx; if (p.y + p.radius > pjs.canvas.h) p.vy = -p.vy; else if (p.y - p.radius < 0) p.vy = -p.vy; break; } /* events */ if(isinarray('grab', pjs.interactivity.events.onhover.mode)){ pjs.fn.modes.grabparticle(p); } if(isinarray('bubble', pjs.interactivity.events.onhover.mode) || isinarray('bubble', pjs.interactivity.events.onclick.mode)){ pjs.fn.modes.bubbleparticle(p); } if(isinarray('repulse', pjs.interactivity.events.onhover.mode) || isinarray('repulse', pjs.interactivity.events.onclick.mode)){ pjs.fn.modes.repulseparticle(p); } /* interaction auto between particles */ if(pjs.particles.line_linked.enable || pjs.particles.move.attract.enable){ for(var j = i + 1; j < pjs.particles.array.length; j++){ var p2 = pjs.particles.array[j]; /* link particles */ if(pjs.particles.line_linked.enable){ pjs.fn.interact.linkparticles(p,p2); } /* attract particles */ if(pjs.particles.move.attract.enable){ pjs.fn.interact.attractparticles(p,p2); } /* bounce particles */ if(pjs.particles.move.bounce){ pjs.fn.interact.bounceparticles(p,p2); } } } } }; pjs.fn.particlesdraw = function(){ /* clear canvas */ pjs.canvas.ctx.clearrect(0, 0, pjs.canvas.w, pjs.canvas.h); /* update each particles param */ pjs.fn.particlesupdate(); /* draw each particle */ for(var i = 0; i < pjs.particles.array.length; i++){ var p = pjs.particles.array[i]; p.draw(); } }; pjs.fn.particlesempty = function(){ pjs.particles.array = []; }; pjs.fn.particlesrefresh = function(){ /* init all */ cancelrequestanimframe(pjs.fn.checkanimframe); cancelrequestanimframe(pjs.fn.drawanimframe); pjs.tmp.source_svg = undefined; pjs.tmp.img_obj = undefined; pjs.tmp.count_svg = 0; pjs.fn.particlesempty(); pjs.fn.canvasclear(); /* restart */ pjs.fn.vendors.start(); }; /* ---------- pjs functions - particles interaction ------------ */ pjs.fn.interact.linkparticles = function(p1, p2){ var dx = p1.x - p2.x, dy = p1.y - p2.y, dist = math.sqrt(dx*dx + dy*dy); /* draw a line between p1 and p2 if the distance between them is under the config distance */ if(dist <= pjs.particles.line_linked.distance){ var opacity_line = pjs.particles.line_linked.opacity - (dist / (1/pjs.particles.line_linked.opacity)) / pjs.particles.line_linked.distance; if(opacity_line > 0){ /* style */ var color_line = pjs.particles.line_linked.color_rgb_line; pjs.canvas.ctx.strokestyle = 'rgba('+color_line.r+','+color_line.g+','+color_line.b+','+opacity_line+')'; pjs.canvas.ctx.linewidth = pjs.particles.line_linked.width; //pjs.canvas.ctx.linecap = 'round'; /* performance issue */ /* path */ pjs.canvas.ctx.beginpath(); pjs.canvas.ctx.moveto(p1.x, p1.y); pjs.canvas.ctx.lineto(p2.x, p2.y); pjs.canvas.ctx.stroke(); pjs.canvas.ctx.closepath(); } } }; pjs.fn.interact.attractparticles = function(p1, p2){ /* condensed particles */ var dx = p1.x - p2.x, dy = p1.y - p2.y, dist = math.sqrt(dx*dx + dy*dy); if(dist <= pjs.particles.line_linked.distance){ var ax = dx/(pjs.particles.move.attract.rotatex*1000), ay = dy/(pjs.particles.move.attract.rotatey*1000); p1.vx -= ax; p1.vy -= ay; p2.vx += ax; p2.vy += ay; } } pjs.fn.interact.bounceparticles = function(p1, p2){ var dx = p1.x - p2.x, dy = p1.y - p2.y, dist = math.sqrt(dx*dx + dy*dy), dist_p = p1.radius+p2.radius; if(dist <= dist_p){ p1.vx = -p1.vx; p1.vy = -p1.vy; p2.vx = -p2.vx; p2.vy = -p2.vy; } } /* ---------- pjs functions - modes events ------------ */ pjs.fn.modes.pushparticles = function(nb, pos){ pjs.tmp.pushing = true; for(var i = 0; i < nb; i++){ pjs.particles.array.push( new pjs.fn.particle( pjs.particles.color, pjs.particles.opacity.value, { 'x': pos ? pos.pos_x : math.random() * pjs.canvas.w, 'y': pos ? pos.pos_y : math.random() * pjs.canvas.h } ) ) if(i == nb-1){ if(!pjs.particles.move.enable){ pjs.fn.particlesdraw(); } pjs.tmp.pushing = false; } } }; pjs.fn.modes.removeparticles = function(nb){ pjs.particles.array.splice(0, nb); if(!pjs.particles.move.enable){ pjs.fn.particlesdraw(); } }; pjs.fn.modes.bubbleparticle = function(p){ /* on hover event */ if(pjs.interactivity.events.onhover.enable && isinarray('bubble', pjs.interactivity.events.onhover.mode)){ var dx_mouse = p.x - pjs.interactivity.mouse.pos_x, dy_mouse = p.y - pjs.interactivity.mouse.pos_y, dist_mouse = math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse), ratio = 1 - dist_mouse / pjs.interactivity.modes.bubble.distance; function init(){ p.opacity_bubble = p.opacity; p.radius_bubble = p.radius; } /* mousemove - check ratio */ if(dist_mouse <= pjs.interactivity.modes.bubble.distance){ if(ratio >= 0 && pjs.interactivity.status == 'mousemove'){ /* size */ if(pjs.interactivity.modes.bubble.size != pjs.particles.size.value){ if(pjs.interactivity.modes.bubble.size > pjs.particles.size.value){ var size = p.radius + (pjs.interactivity.modes.bubble.size*ratio); if(size >= 0){ p.radius_bubble = size; } }else{ var dif = p.radius - pjs.interactivity.modes.bubble.size, size = p.radius - (dif*ratio); if(size > 0){ p.radius_bubble = size; }else{ p.radius_bubble = 0; } } } /* opacity */ if(pjs.interactivity.modes.bubble.opacity != pjs.particles.opacity.value){ if(pjs.interactivity.modes.bubble.opacity > pjs.particles.opacity.value){ var opacity = pjs.interactivity.modes.bubble.opacity*ratio; if(opacity > p.opacity && opacity <= pjs.interactivity.modes.bubble.opacity){ p.opacity_bubble = opacity; } }else{ var opacity = p.opacity - (pjs.particles.opacity.value-pjs.interactivity.modes.bubble.opacity)*ratio; if(opacity < p.opacity && opacity >= pjs.interactivity.modes.bubble.opacity){ p.opacity_bubble = opacity; } } } } }else{ init(); } /* mouseleave */ if(pjs.interactivity.status == 'mouseleave'){ init(); } } /* on click event */ else if(pjs.interactivity.events.onclick.enable && isinarray('bubble', pjs.interactivity.events.onclick.mode)){ if(pjs.tmp.bubble_clicking){ var dx_mouse = p.x - pjs.interactivity.mouse.click_pos_x, dy_mouse = p.y - pjs.interactivity.mouse.click_pos_y, dist_mouse = math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse), time_spent = (new date().gettime() - pjs.interactivity.mouse.click_time)/1000; if(time_spent > pjs.interactivity.modes.bubble.duration){ pjs.tmp.bubble_duration_end = true; } if(time_spent > pjs.interactivity.modes.bubble.duration*2){ pjs.tmp.bubble_clicking = false; pjs.tmp.bubble_duration_end = false; } } function process(bubble_param, particles_param, p_obj_bubble, p_obj, id){ if(bubble_param != particles_param){ if(!pjs.tmp.bubble_duration_end){ if(dist_mouse <= pjs.interactivity.modes.bubble.distance){ if(p_obj_bubble != undefined) var obj = p_obj_bubble; else var obj = p_obj; if(obj != bubble_param){ var value = p_obj - (time_spent * (p_obj - bubble_param) / pjs.interactivity.modes.bubble.duration); if(id == 'size') p.radius_bubble = value; if(id == 'opacity') p.opacity_bubble = value; } }else{ if(id == 'size') p.radius_bubble = undefined; if(id == 'opacity') p.opacity_bubble = undefined; } }else{ if(p_obj_bubble != undefined){ var value_tmp = p_obj - (time_spent * (p_obj - bubble_param) / pjs.interactivity.modes.bubble.duration), dif = bubble_param - value_tmp; value = bubble_param + dif; if(id == 'size') p.radius_bubble = value; if(id == 'opacity') p.opacity_bubble = value; } } } } if(pjs.tmp.bubble_clicking){ /* size */ process(pjs.interactivity.modes.bubble.size, pjs.particles.size.value, p.radius_bubble, p.radius, 'size'); /* opacity */ process(pjs.interactivity.modes.bubble.opacity, pjs.particles.opacity.value, p.opacity_bubble, p.opacity, 'opacity'); } } }; pjs.fn.modes.repulseparticle = function(p){ if(pjs.interactivity.events.onhover.enable && isinarray('repulse', pjs.interactivity.events.onhover.mode) && pjs.interactivity.status == 'mousemove') { var dx_mouse = p.x - pjs.interactivity.mouse.pos_x, dy_mouse = p.y - pjs.interactivity.mouse.pos_y, dist_mouse = math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse); var normvec = {x: dx_mouse/dist_mouse, y: dy_mouse/dist_mouse}, repulseradius = pjs.interactivity.modes.repulse.distance, velocity = 100, repulsefactor = clamp((1/repulseradius)*(-1*math.pow(dist_mouse/repulseradius,2)+1)*repulseradius*velocity, 0, 50); var pos = { x: p.x + normvec.x * repulsefactor, y: p.y + normvec.y * repulsefactor } if(pjs.particles.move.out_mode == 'bounce'){ if(pos.x - p.radius > 0 && pos.x + p.radius < pjs.canvas.w) p.x = pos.x; if(pos.y - p.radius > 0 && pos.y + p.radius < pjs.canvas.h) p.y = pos.y; }else{ p.x = pos.x; p.y = pos.y; } } else if(pjs.interactivity.events.onclick.enable && isinarray('repulse', pjs.interactivity.events.onclick.mode)) { if(!pjs.tmp.repulse_finish){ pjs.tmp.repulse_count++; if(pjs.tmp.repulse_count == pjs.particles.array.length){ pjs.tmp.repulse_finish = true; } } if(pjs.tmp.repulse_clicking){ var repulseradius = math.pow(pjs.interactivity.modes.repulse.distance/6, 3); var dx = pjs.interactivity.mouse.click_pos_x - p.x, dy = pjs.interactivity.mouse.click_pos_y - p.y, d = dx*dx + dy*dy; var force = -repulseradius / d * 1; function process(){ var f = math.atan2(dy,dx); p.vx = force * math.cos(f); p.vy = force * math.sin(f); if(pjs.particles.move.out_mode == 'bounce'){ var pos = { x: p.x + p.vx, y: p.y + p.vy } if (pos.x + p.radius > pjs.canvas.w) p.vx = -p.vx; else if (pos.x - p.radius < 0) p.vx = -p.vx; if (pos.y + p.radius > pjs.canvas.h) p.vy = -p.vy; else if (pos.y - p.radius < 0) p.vy = -p.vy; } } // default if(d <= repulseradius){ process(); } // bang - slow motion mode // if(!pjs.tmp.repulse_finish){ // if(d <= repulseradius){ // process(); // } // }else{ // process(); // } }else{ if(pjs.tmp.repulse_clicking == false){ p.vx = p.vx_i; p.vy = p.vy_i; } } } } pjs.fn.modes.grabparticle = function(p){ if(pjs.interactivity.events.onhover.enable && pjs.interactivity.status == 'mousemove'){ var dx_mouse = p.x - pjs.interactivity.mouse.pos_x, dy_mouse = p.y - pjs.interactivity.mouse.pos_y, dist_mouse = math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse); /* draw a line between the cursor and the particle if the distance between them is under the config distance */ if(dist_mouse <= pjs.interactivity.modes.grab.distance){ var opacity_line = pjs.interactivity.modes.grab.line_linked.opacity - (dist_mouse / (1/pjs.interactivity.modes.grab.line_linked.opacity)) / pjs.interactivity.modes.grab.distance; if(opacity_line > 0){ /* style */ var color_line = pjs.particles.line_linked.color_rgb_line; pjs.canvas.ctx.strokestyle = 'rgba('+color_line.r+','+color_line.g+','+color_line.b+','+opacity_line+')'; pjs.canvas.ctx.linewidth = pjs.particles.line_linked.width; //pjs.canvas.ctx.linecap = 'round'; /* performance issue */ /* path */ pjs.canvas.ctx.beginpath(); pjs.canvas.ctx.moveto(p.x, p.y); pjs.canvas.ctx.lineto(pjs.interactivity.mouse.pos_x, pjs.interactivity.mouse.pos_y); pjs.canvas.ctx.stroke(); pjs.canvas.ctx.closepath(); } } } }; /* ---------- pjs functions - vendors ------------ */ pjs.fn.vendors.eventslisteners = function(){ /* events target element */ if(pjs.interactivity.detect_on == 'window'){ pjs.interactivity.el = window; }else{ pjs.interactivity.el = pjs.canvas.el; } /* detect mouse pos - on hover / click event */ if(pjs.interactivity.events.onhover.enable || pjs.interactivity.events.onclick.enable){ /* el on mousemove */ pjs.interactivity.el.addeventlistener('mousemove', function(e){ if(pjs.interactivity.el == window){ var pos_x = e.clientx, pos_y = e.clienty; } else{ var pos_x = e.offsetx || e.clientx, pos_y = e.offsety || e.clienty; } pjs.interactivity.mouse.pos_x = pos_x; pjs.interactivity.mouse.pos_y = pos_y; if(pjs.tmp.retina){ pjs.interactivity.mouse.pos_x *= pjs.canvas.pxratio; pjs.interactivity.mouse.pos_y *= pjs.canvas.pxratio; } pjs.interactivity.status = 'mousemove'; }); /* el on onmouseleave */ pjs.interactivity.el.addeventlistener('mouseleave', function(e){ pjs.interactivity.mouse.pos_x = null; pjs.interactivity.mouse.pos_y = null; pjs.interactivity.status = 'mouseleave'; }); } /* on click event */ if(pjs.interactivity.events.onclick.enable){ pjs.interactivity.el.addeventlistener('click', function(){ pjs.interactivity.mouse.click_pos_x = pjs.interactivity.mouse.pos_x; pjs.interactivity.mouse.click_pos_y = pjs.interactivity.mouse.pos_y; pjs.interactivity.mouse.click_time = new date().gettime(); if(pjs.interactivity.events.onclick.enable){ switch(pjs.interactivity.events.onclick.mode){ case 'push': if(pjs.particles.move.enable){ pjs.fn.modes.pushparticles(pjs.interactivity.modes.push.particles_nb, pjs.interactivity.mouse); }else{ if(pjs.interactivity.modes.push.particles_nb == 1){ pjs.fn.modes.pushparticles(pjs.interactivity.modes.push.particles_nb, pjs.interactivity.mouse); } else if(pjs.interactivity.modes.push.particles_nb > 1){ pjs.fn.modes.pushparticles(pjs.interactivity.modes.push.particles_nb); } } break; case 'remove': pjs.fn.modes.removeparticles(pjs.interactivity.modes.remove.particles_nb); break; case 'bubble': pjs.tmp.bubble_clicking = true; break; case 'repulse': pjs.tmp.repulse_clicking = true; pjs.tmp.repulse_count = 0; pjs.tmp.repulse_finish = false; settimeout(function(){ pjs.tmp.repulse_clicking = false; }, pjs.interactivity.modes.repulse.duration*1000) break; } } }); } }; pjs.fn.vendors.densityautoparticles = function(){ if(pjs.particles.number.density.enable){ /* calc area */ var area = pjs.canvas.el.width * pjs.canvas.el.height / 1000; if(pjs.tmp.retina){ area = area/(pjs.canvas.pxratio*2); } /* calc number of particles based on density area */ var nb_particles = area * pjs.particles.number.value / pjs.particles.number.density.value_area; /* add or remove x particles */ var missing_particles = pjs.particles.array.length - nb_particles; if(missing_particles < 0) pjs.fn.modes.pushparticles(math.abs(missing_particles)); else pjs.fn.modes.removeparticles(missing_particles); } }; pjs.fn.vendors.checkoverlap = function(p1, position){ for(var i = 0; i < pjs.particles.array.length; i++){ var p2 = pjs.particles.array[i]; var dx = p1.x - p2.x, dy = p1.y - p2.y, dist = math.sqrt(dx*dx + dy*dy); if(dist <= p1.radius + p2.radius){ p1.x = position ? position.x : math.random() * pjs.canvas.w; p1.y = position ? position.y : math.random() * pjs.canvas.h; pjs.fn.vendors.checkoverlap(p1); } } }; pjs.fn.vendors.createsvgimg = function(p){ /* set color to svg element */ var svgxml = pjs.tmp.source_svg, rgbhex = /#([0-9a-f]{3,6})/gi, coloredsvgxml = svgxml.replace(rgbhex, function (m, r, g, b) { if(p.color.rgb){ var color_value = 'rgba('+p.color.rgb.r+','+p.color.rgb.g+','+p.color.rgb.b+','+p.opacity+')'; }else{ var color_value = 'hsla('+p.color.hsl.h+','+p.color.hsl.s+'%,'+p.color.hsl.l+'%,'+p.opacity+')'; } return color_value; }); /* prepare to create img with colored svg */ var svg = new blob([coloredsvgxml], {type: 'image/svg+xml;charset=utf-8'}), domurl = window.url || window.webkiturl || window, url = domurl.createobjecturl(svg); /* create particle img obj */ var img = new image(); img.addeventlistener('load', function(){ p.img.obj = img; p.img.loaded = true; domurl.revokeobjecturl(url); pjs.tmp.count_svg++; }); img.src = url; }; pjs.fn.vendors.destroypjs = function(){ cancelanimationframe(pjs.fn.drawanimframe); canvas_el.remove(); pjsdom = null; }; pjs.fn.vendors.drawshape = function(c, startx, starty, sidelength, sidecountnumerator, sidecountdenominator){ // by programming thomas - https://programmingthomas.wordpress.com/2013/04/03/n-sided-shapes/ var sidecount = sidecountnumerator * sidecountdenominator; var decimalsides = sidecountnumerator / sidecountdenominator; var interiorangledegrees = (180 * (decimalsides - 2)) / decimalsides; var interiorangle = math.pi - math.pi * interiorangledegrees / 180; // convert to radians c.save(); c.beginpath(); c.translate(startx, starty); c.moveto(0,0); for (var i = 0; i < sidecount; i++) { c.lineto(sidelength,0); c.translate(sidelength,0); c.rotate(interiorangle); } //c.stroke(); c.fill(); c.restore(); }; pjs.fn.vendors.exportimg = function(){ window.open(pjs.canvas.el.todataurl('image/png'), '_blank'); }; pjs.fn.vendors.loadimg = function(type){ pjs.tmp.img_error = undefined; if(pjs.particles.shape.image.src != ''){ if(type == 'svg'){ var xhr = new xmlhttprequest(); xhr.open('get', pjs.particles.shape.image.src); xhr.onreadystatechange = function (data) { if(xhr.readystate == 4){ if(xhr.status == 200){ pjs.tmp.source_svg = data.currenttarget.response; pjs.fn.vendors.checkbeforedraw(); }else{ console.log('error pjs - image not found'); pjs.tmp.img_error = true; } } } xhr.send(); }else{ var img = new image(); img.addeventlistener('load', function(){ pjs.tmp.img_obj = img; pjs.fn.vendors.checkbeforedraw(); }); img.src = pjs.particles.shape.image.src; } }else{ console.log('error pjs - no image.src'); pjs.tmp.img_error = true; } }; pjs.fn.vendors.draw = function(){ if(pjs.particles.shape.type == 'image'){ if(pjs.tmp.img_type == 'svg'){ if(pjs.tmp.count_svg >= pjs.particles.number.value){ pjs.fn.particlesdraw(); if(!pjs.particles.move.enable) cancelrequestanimframe(pjs.fn.drawanimframe); else pjs.fn.drawanimframe = requestanimframe(pjs.fn.vendors.draw); }else{ //console.log('still loading...'); if(!pjs.tmp.img_error) pjs.fn.drawanimframe = requestanimframe(pjs.fn.vendors.draw); } }else{ if(pjs.tmp.img_obj != undefined){ pjs.fn.particlesdraw(); if(!pjs.particles.move.enable) cancelrequestanimframe(pjs.fn.drawanimframe); else pjs.fn.drawanimframe = requestanimframe(pjs.fn.vendors.draw); }else{ if(!pjs.tmp.img_error) pjs.fn.drawanimframe = requestanimframe(pjs.fn.vendors.draw); } } }else{ pjs.fn.particlesdraw(); if(!pjs.particles.move.enable) cancelrequestanimframe(pjs.fn.drawanimframe); else pjs.fn.drawanimframe = requestanimframe(pjs.fn.vendors.draw); } }; pjs.fn.vendors.checkbeforedraw = function(){ // if shape is image if(pjs.particles.shape.type == 'image'){ if(pjs.tmp.img_type == 'svg' && pjs.tmp.source_svg == undefined){ pjs.tmp.checkanimframe = requestanimframe(check); }else{ //console.log('images loaded! cancel check'); cancelrequestanimframe(pjs.tmp.checkanimframe); if(!pjs.tmp.img_error){ pjs.fn.vendors.init(); pjs.fn.vendors.draw(); } } }else{ pjs.fn.vendors.init(); pjs.fn.vendors.draw(); } }; pjs.fn.vendors.init = function(){ /* init canvas + particles */ pjs.fn.retinainit(); pjs.fn.canvasinit(); pjs.fn.canvassize(); pjs.fn.canvaspaint(); pjs.fn.particlescreate(); pjs.fn.vendors.densityautoparticles(); /* particles.line_linked - convert hex colors to rgb */ pjs.particles.line_linked.color_rgb_line = hextorgb(pjs.particles.line_linked.color); }; pjs.fn.vendors.start = function(){ if(isinarray('image', pjs.particles.shape.type)){ pjs.tmp.img_type = pjs.particles.shape.image.src.substr(pjs.particles.shape.image.src.length - 3); pjs.fn.vendors.loadimg(pjs.tmp.img_type); }else{ pjs.fn.vendors.checkbeforedraw(); } }; /* ---------- pjs - start ------------ */ pjs.fn.vendors.eventslisteners(); pjs.fn.vendors.start(); }; /* ---------- global functions - vendors ------------ */ object.deepextend = function(destination, source) { for (var property in source) { if (source[property] && source[property].constructor && source[property].constructor === object) { destination[property] = destination[property] || {}; arguments.callee(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination; }; window.requestanimframe = (function(){ return window.requestanimationframe || window.webkitrequestanimationframe || window.mozrequestanimationframe || window.orequestanimationframe || window.msrequestanimationframe || function(callback){ window.settimeout(callback, 1000 / 60); }; })(); window.cancelrequestanimframe = ( function() { return window.cancelanimationframe || window.webkitcancelrequestanimationframe || window.mozcancelrequestanimationframe || window.ocancelrequestanimationframe || window.mscancelrequestanimationframe || cleartimeout } )(); function hextorgb(hex){ // by tim down - http://stackoverflow.com/a/5624139/3493650 // expand shorthand form (e.g. "03f") to full form (e.g. "0033ff") var shorthandregex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandregex, function(m, r, g, b) { return r + r + g + g + b + b; }); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseint(result[1], 16), g: parseint(result[2], 16), b: parseint(result[3], 16) } : null; }; function clamp(number, min, max) { return math.min(math.max(number, min), max); }; function isinarray(value, array) { return array.indexof(value) > -1; } /* ---------- particles.js functions - start ------------ */ window.pjsdom = []; window.particlesjs = function(tag_id, params){ //console.log(params); /* no string id? so it's object params, and set the id with default id */ if(typeof(tag_id) != 'string'){ params = tag_id; tag_id = 'particles-js'; } /* no id? set the id to default id */ if(!tag_id){ tag_id = 'particles-js'; } /* pjs elements */ var pjs_tag = document.getelementbyid(tag_id), pjs_canvas_class = 'particles-js-canvas-el', exist_canvas = pjs_tag.getelementsbyclassname(pjs_canvas_class); /* remove canvas if exists into the pjs target tag */ if(exist_canvas.length){ while(exist_canvas.length > 0){ pjs_tag.removechild(exist_canvas[0]); } } /* create canvas element */ var canvas_el = document.createelement('canvas'); canvas_el.classname = pjs_canvas_class; /* set size canvas */ canvas_el.style.width = "100%"; canvas_el.style.height = "100%"; /* append canvas */ var canvas = document.getelementbyid(tag_id).appendchild(canvas_el); /* launch particle.js */ if(canvas != null){ pjsdom.push(new pjs(tag_id, params)); } }; window.particlesjs.load = function(tag_id, path_config_json, callback){ /* load json config */ var xhr = new xmlhttprequest(); xhr.open('get', path_config_json); xhr.onreadystatechange = function (data) { if(xhr.readystate == 4){ if(xhr.status == 200){ var params = json.parse(data.currenttarget.response); window.particlesjs(tag_id, params); if(callback) callback(); }else{ console.log('error pjs - xmlhttprequest status: '+xhr.status); console.log('error pjs - file config not found'); } } }; xhr.send(); };