/*! * particleground * * @author jonathan nicol - @mrjnicol * @version 1.1.0 * @description creates a canvas based particle system background * * inspired by http://requestlab.fr/ and http://disruptivebydesign.com/ */ ;(function(window, document) { "use strict"; var pluginname = 'particleground'; // http://youmightnotneedjquery.com/#deep_extend function extend(out) { out = out || {}; for (var i = 1; i < arguments.length; i++) { var obj = arguments[i]; if (!obj) continue; for (var key in obj) { if (obj.hasownproperty(key)) { if (typeof obj[key] === 'object') deepextend(out[key], obj[key]); else out[key] = obj[key]; } } } return out; }; var $ = window.jquery; function plugin(element, options) { var canvassupport = !!document.createelement('canvas').getcontext; var canvas; var ctx; var particles = []; var raf; var mousex = 0; var mousey = 0; var winw; var winh; var desktop = !navigator.useragent.match(/(iphone|ipod|ipad|android|blackberry|bb10|mobi|tablet|opera mini|nexus 7)/i); var orientationsupport = !!window.deviceorientationevent; var tiltx = 0; var pointerx; var pointery; var tilty = 0; var paused = false; options = extend({}, window[pluginname].defaults, options); /** * init */ function init() { if (!canvassupport) { return; } //create canvas canvas = document.createelement('canvas'); canvas.classname = 'pg-canvas'; canvas.style.display = 'block'; element.insertbefore(canvas, element.firstchild); ctx = canvas.getcontext('2d'); stylecanvas(); // create particles var numparticles = math.round((canvas.width * canvas.height) / options.density); for (var i = 0; i < numparticles; i++) { var p = new particle(); p.setstackpos(i); particles.push(p); }; window.addeventlistener('resize', function() { resizehandler(); }, false); // document.addeventlistener('mousemove', function(e) { // mousex = e.pagex; // mousey = e.pagey; // }, false); if (orientationsupport && !desktop) { window.addeventlistener('deviceorientation', function () { // contrain tilt range to [-30,30] tilty = math.min(math.max(-event.beta, -30), 30); tiltx = math.min(math.max(-event.gamma, -30), 30); }, true); } draw(); hook('oninit'); } /** * style the canvas */ function stylecanvas() { canvas.width = element.offsetwidth; canvas.height = element.offsetheight; ctx.fillstyle = options.dotcolor; ctx.strokestyle = options.linecolor; ctx.linewidth = options.linewidth; } /** * draw particles */ function draw() { if (!canvassupport) { return; } winw = window.innerwidth; winh = window.innerheight; // wipe canvas ctx.clearrect(0, 0, canvas.width, canvas.height); // update particle positions for (var i = 0; i < particles.length; i++) { particles[i].updateposition(); }; // draw particles for (var i = 0; i < particles.length; i++) { particles[i].draw(); }; // call this function next time screen is redrawn if (!paused) { raf = requestanimationframe(draw); } } /** * add/remove particles. */ function resizehandler() { // resize the canvas stylecanvas(); var elwidth = element.offsetwidth; var elheight = element.offsetheight; // remove particles that are outside the canvas for (var i = particles.length - 1; i >= 0; i--) { if (particles[i].position.x > elwidth || particles[i].position.y > elheight) { particles.splice(i, 1); } }; // adjust particle density var numparticles = math.round((canvas.width * canvas.height) / options.density); if (numparticles > particles.length) { while (numparticles > particles.length) { var p = new particle(); particles.push(p); } } else if (numparticles < particles.length) { particles.splice(numparticles); } // re-index particles for (i = particles.length - 1; i >= 0; i--) { particles[i].setstackpos(i); }; } /** * pause particle system */ function pause() { paused = true; } /** * start particle system */ function start() { paused = false; draw(); } /** * particle */ function particle() { this.stackpos; this.active = true; this.layer = math.ceil(math.random() * 3); this.parallaxoffsetx = 0; this.parallaxoffsety = 0; // initial particle position this.position = { x: math.ceil(math.random() * canvas.width), y: math.ceil(math.random() * canvas.height) } // random particle speed, within min and max values this.speed = {} switch (options.directionx) { case 'left': this.speed.x = +(-options.maxspeedx + (math.random() * options.maxspeedx) - options.minspeedx).tofixed(2); break; case 'right': this.speed.x = +((math.random() * options.maxspeedx) + options.minspeedx).tofixed(2); break; default: this.speed.x = +((-options.maxspeedx / 2) + (math.random() * options.maxspeedx)).tofixed(2); this.speed.x += this.speed.x > 0 ? options.minspeedx : -options.minspeedx; break; } switch (options.directiony) { case 'up': this.speed.y = +(-options.maxspeedy + (math.random() * options.maxspeedy) - options.minspeedy).tofixed(2); break; case 'down': this.speed.y = +((math.random() * options.maxspeedy) + options.minspeedy).tofixed(2); break; default: this.speed.y = +((-options.maxspeedy / 2) + (math.random() * options.maxspeedy)).tofixed(2); this.speed.x += this.speed.y > 0 ? options.minspeedy : -options.minspeedy; break; } } /** * draw particle */ particle.prototype.draw = function() { // draw circle ctx.beginpath(); ctx.arc(this.position.x + this.parallaxoffsetx, this.position.y + this.parallaxoffsety, options.particleradius / 2, 0, math.pi * 2, true); ctx.closepath(); ctx.fill(); // draw lines ctx.beginpath(); // iterate over all particles which are higher in the stack than this one for (var i = particles.length - 1; i > this.stackpos; i--) { var p2 = particles[i]; // pythagorus theorum to get distance between two points var a = this.position.x - p2.position.x var b = this.position.y - p2.position.y var dist = math.sqrt((a * a) + (b * b)).tofixed(2); // if the two particles are in proximity, join them if (dist < options.proximity) { ctx.moveto(this.position.x + this.parallaxoffsetx, this.position.y + this.parallaxoffsety); if (options.curvedlines) { ctx.quadraticcurveto(math.max(p2.position.x, p2.position.x), math.min(p2.position.y, p2.position.y), p2.position.x + p2.parallaxoffsetx, p2.position.y + p2.parallaxoffsety); } else { ctx.lineto(p2.position.x + p2.parallaxoffsetx, p2.position.y + p2.parallaxoffsety); } } } ctx.stroke(); ctx.closepath(); } /** * update particle position */ particle.prototype.updateposition = function() { if (options.parallax) { if (orientationsupport && !desktop) { // map tiltx range [-30,30] to range [0,winw] var ratiox = (winw - 0) / (30 - -30); pointerx = (tiltx - -30) * ratiox + 0; // map tilty range [-30,30] to range [0,winh] var ratioy = (winh - 0) / (30 - -30); pointery = (tilty - -30) * ratioy + 0; } else { pointerx = mousex; pointery = mousey; } // calculate parallax offsets this.parallaxtargx = (pointerx - (winw / 2)) / (options.parallaxmultiplier * this.layer); //this.parallaxoffsetx += (this.parallaxtargx - this.parallaxoffsetx) / 10; // easing equation this.parallaxtargy = (pointery - (winh / 2)) / (options.parallaxmultiplier * this.layer); //this.parallaxoffsety += (this.parallaxtargy - this.parallaxoffsety) / 10; // easing equation } var elwidth = element.offsetwidth; var elheight = element.offsetheight; switch (options.directionx) { case 'left': if (this.position.x + this.speed.x + this.parallaxoffsetx < 0) { this.position.x = elwidth - this.parallaxoffsetx; } break; case 'right': if (this.position.x + this.speed.x + this.parallaxoffsetx > elwidth) { this.position.x = 0 - this.parallaxoffsetx; } break; default: // if particle has reached edge of canvas, reverse its direction if (this.position.x + this.speed.x + this.parallaxoffsetx > elwidth || this.position.x + this.speed.x + this.parallaxoffsetx < 0) { this.speed.x = -this.speed.x; } break; } switch (options.directiony) { case 'up': if (this.position.y + this.speed.y + this.parallaxoffsety < 0) { this.position.y = elheight - this.parallaxoffsety; } break; case 'down': if (this.position.y + this.speed.y + this.parallaxoffsety > elheight) { this.position.y = 0 - this.parallaxoffsety; } break; default: // if particle has reached edge of canvas, reverse its direction if (this.position.y + this.speed.y + this.parallaxoffsety > elheight || this.position.y + this.speed.y + this.parallaxoffsety < 0) { this.speed.y = -this.speed.y; } break; } // move particle this.position.x += this.speed.x; this.position.y += this.speed.y; } /** * setter: particle stacking position */ particle.prototype.setstackpos = function(i) { this.stackpos = i; } function option (key, val) { if (val) { options[key] = val; } else { return options[key]; } } function destroy() { console.log('destroy'); canvas.parentnode.removechild(canvas); hook('ondestroy'); if ($) { $(element).removedata('plugin_' + pluginname); } } function hook(hookname) { if (options[hookname] !== undefined) { options[hookname].call(element); } } init(); return { option: option, destroy: destroy, start: start, pause: pause }; } window[pluginname] = function(elem, options) { return new plugin(elem, options); }; window[pluginname].defaults = { minspeedx: 0.1, maxspeedx: 0.7, minspeedy: 0.1, maxspeedy: 0.7, directionx: 'center', // 'center', 'left' or 'right'. 'center' = dots bounce off edges directiony: 'center', // 'center', 'up' or 'down'. 'center' = dots bounce off edges density: 10000, // how many particles will be generated: one particle every n pixels dotcolor: '#666666', linecolor: '#666666', particleradius: 7, // dot size linewidth: 1, curvedlines: false, proximity: 50, // how close two dots need to be before they join parallax: true, parallaxmultiplier: 5, // the lower the number, the more extreme the parallax effect oninit: function() {}, ondestroy: function() {} }; // nothing wrong with hooking into jquery if it's there... if ($) { $.fn[pluginname] = function(options) { if (typeof arguments[0] === 'string') { var methodname = arguments[0]; var args = array.prototype.slice.call(arguments, 1); var returnval; this.each(function() { if ($.data(this, 'plugin_' + pluginname) && typeof $.data(this, 'plugin_' + pluginname)[methodname] === 'function') { returnval = $.data(this, 'plugin_' + pluginname)[methodname].apply(this, args); } }); if (returnval !== undefined){ return returnval; } else { return this; } } else if (typeof options === "object" || !options) { return this.each(function() { if (!$.data(this, 'plugin_' + pluginname)) { $.data(this, 'plugin_' + pluginname, new plugin(this, options)); } }); } }; } })(window, document); /** * requestanimationframe polyfill by erik möller. fixes from paul irish and tino zijdel * @see: http://paulirish.com/2011/requestanimationframe-for-smart-animating/ * @see: http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating * @license: mit license */ (function() { var lasttime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestanimationframe; ++x) { window.requestanimationframe = window[vendors[x]+'requestanimationframe']; window.cancelanimationframe = window[vendors[x]+'cancelanimationframe'] || window[vendors[x]+'cancelrequestanimationframe']; } if (!window.requestanimationframe) window.requestanimationframe = function(callback, element) { var currtime = new date().gettime(); var timetocall = math.max(0, 16 - (currtime - lasttime)); var id = window.settimeout(function() { callback(currtime + timetocall); }, timetocall); lasttime = currtime + timetocall; return id; }; if (!window.cancelanimationframe) window.cancelanimationframe = function(id) { cleartimeout(id); }; }());