planet-launcher/particles.js

559 lines
15 KiB
JavaScript

/*!
* A lightweight, dependency-free and responsive javascript plugin for particle backgrounds.
*
* @author Marc Bruederlin <hello@marcbruederlin.com>
* @version 2.2.3
* @license MIT
* @see https://github.com/marcbruederlin/particles.js
*/
/* exported Particles */
var Particles = (function (window, document) {
'use strict';
var Plugin, Particle = {};
function particleCompareFunc(p1, p2) {
if (p1.x < p2.x) {
return -1;
} else if (p1.x > p2.x) {
return 1;
} else if (p1.y < p2.y) {
return -1;
} else if (p1.y > p2.y) {
return 1;
}
return 0;
}
/**
* Represents the plugin.
*
* @constructor
*/
Plugin = (function () {
function Plugin() {
var _ = this;
_.defaults = {
responsive: null,
selector: null,
maxParticles: 100,
sizeVariations: 3,
showParticles: true,
speed: 0.5,
color: '#000000',
minDistance: 120,
connectParticles: false,
};
_.element = null;
_.context = null;
_.ratio = null;
_.breakpoints = [];
_.activeBreakpoint = null;
_.breakpointSettings = [];
_.originalSettings = null;
_.storage = [];
_.usingPolyfill = false;
}
return Plugin;
}());
/**
* Public mehtod to initialize the plugin with user settings.
*
* @public
* @param {object} settings
*/
Plugin.prototype.init = function (settings) {
var _ = this;
_.options = _._extend(_.defaults, settings);
_.originalSettings = JSON.parse(JSON.stringify(_.options));
_._animate = _._animate.bind(_);
_._initializeCanvas();
_._initializeEvents();
_._registerBreakpoints();
_._checkResponsive();
_._initializeStorage();
_._animate();
return _;
};
/**
* Public method to destroy the plugin.
*
* @public
*/
Plugin.prototype.destroy = function () {
var _ = this;
_.storage = [];
_.element.remove();
window.removeEventListener('resize', _.listener, false);
window.clearTimeout(_._animation);
cancelAnimationFrame(_._animation);
};
/**
* Setup the canvas element.
*
* @private
*/
Plugin.prototype._initializeCanvas = function () {
var _ = this, devicePixelRatio, backingStoreRatio;
if (!_.options.selector) {
console.warn('particles.js: No selector specified! Check https://github.com/marcbruederlin/particles.js#options');
return false;
}
_.element = document.querySelector(_.options.selector);
_.context = _.element.getContext('2d');
devicePixelRatio = window.devicePixelRatio || 1;
backingStoreRatio = _.context.webkitBackingStorePixelRatio || _.context.mozBackingStorePixelRatio || _.context.msBackingStorePixelRatio ||
_.context.oBackingStorePixelRatio || _.context.backingStorePixelRatio || 1;
_.ratio = devicePixelRatio / backingStoreRatio;
_.element.width = (_.element.offsetParent) ? _.element.offsetParent.clientWidth * _.ratio : _.element.clientWidth * _.ratio;
if (_.element.offsetParent && _.element.offsetParent.nodeName === 'BODY') {
_.element.height = window.innerHeight * _.ratio;
} else {
_.element.height = (_.element.offsetParent) ? _.element.offsetParent.clientHeight * _.ratio : _.element.clientHeight * _.ratio;
}
_.element.style.width = '100%';
_.element.style.height = '100%';
_.context.scale(_.ratio, _.ratio);
};
/**
* Register event listeners.
*
* @private
*/
Plugin.prototype._initializeEvents = function () {
var _ = this;
_.listener = function () { _._resize(); }.bind(this);
window.addEventListener('resize', _.listener, false);
};
/**
* Initialize the particle storage.
*
* @private
*/
Plugin.prototype._initializeStorage = function () {
var _ = this;
_.storage = [];
for (var i = _.options.maxParticles; i--;) {
_.storage.push(new Particle(_.context, _.options));
}
};
/**
* Register responsive breakpoints if the user declared some.
*
* @private
*/
Plugin.prototype._registerBreakpoints = function () {
var _ = this, breakpoint, currentBreakpoint, l, responsiveSettings = _.options.responsive || null;
if (typeof responsiveSettings === 'object' && responsiveSettings !== null && responsiveSettings.length) {
for (breakpoint in responsiveSettings) {
l = _.breakpoints.length - 1;
currentBreakpoint = responsiveSettings[breakpoint].breakpoint;
if (responsiveSettings.hasOwnProperty(breakpoint)) {
while (l >= 0) {
if (_.breakpoints[l] && _.breakpoints[l] === currentBreakpoint) {
_.breakpoints.splice(l, 1);
}
l--;
}
_.breakpoints.push(currentBreakpoint);
_.breakpointSettings[currentBreakpoint] = responsiveSettings[breakpoint].options;
}
}
_.breakpoints.sort(function (a, b) {
return b - a;
});
}
};
/**
* Check if a breakpoint is active and load the breakpoints options.
*
* @private
*/
Plugin.prototype._checkResponsive = function () {
var _ = this, breakpoint, targetBreakpoint = false, windowWidth = window.innerWidth;
if (_.options.responsive && _.options.responsive.length && _.options.responsive !== null) {
targetBreakpoint = null;
for (breakpoint in _.breakpoints) {
if (_.breakpoints.hasOwnProperty(breakpoint)) {
if (windowWidth <= _.breakpoints[breakpoint]) {
targetBreakpoint = _.breakpoints[breakpoint];
}
}
}
if (targetBreakpoint !== null) {
_.activeBreakpoint = targetBreakpoint;
_.options = _._extend(_.options, _.breakpointSettings[targetBreakpoint]);
} else {
if (_.activeBreakpoint !== null) {
_.activeBreakpoint = null;
targetBreakpoint = null;
_.options = _._extend(_.options, _.originalSettings);
}
}
}
};
/**
* Rebuild the storage and update the canvas.
*
* @private
*/
Plugin.prototype._refresh = function () {
var _ = this;
_._initializeStorage();
_._draw();
};
/**
* Kick off various things on window resize.
*
* @private
*/
Plugin.prototype._resize = function () {
var _ = this;
_.element.width = (_.element.offsetParent) ? _.element.offsetParent.clientWidth * _.ratio : _.element.clientWidth * _.ratio;
if (_.element.offsetParent && _.element.offsetParent.nodeName === 'BODY') {
_.element.height = window.innerHeight * _.ratio;
} else {
_.element.height = (_.element.offsetParent) ? _.element.offsetParent.clientHeight * _.ratio : _.element.clientHeight * _.ratio;
}
_.context.scale(_.ratio, _.ratio);
clearTimeout(_.windowDelay);
_.windowDelay = window.setTimeout(function () {
_._checkResponsive();
_._refresh();
}, 50);
};
/**
* Animates the plugin particles by calling the draw method.
*
* @private
*/
Plugin.prototype._animate = function () {
var _ = this;
_._draw();
_._animation = window.requestAnimFrame(_._animate);
};
/**
* Restarts the particles animation by calling _animate.
*
* @public
*/
Plugin.prototype.resumeAnimation = function () {
var _ = this;
if (!_._animation) {
_._animate();
}
};
/**
* Pauses/stops the particle animation.
*
* @public
*/
Plugin.prototype.pauseAnimation = function () {
var _ = this;
if (!_._animation) {
return;
}
if (_.usingPolyfill) {
window.clearTimeout(_._animation);
} else {
var cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame;
cancelAnimationFrame(_._animation);
}
_._animation = null;
};
/**
* Draws the plugin particles.
*
* @private
*/
Plugin.prototype._draw = function () {
var _ = this,
element = _.element,
parentWidth = (element.offsetParent) ? element.offsetParent.clientWidth : element.clientWidth,
parentHeight = (element.offsetParent) ? element.offsetParent.clientHeight : element.clientHeight,
showParticles = _.options.showParticles,
storage = _.storage;
if (element.offsetParent && element.offsetParent.nodeName === 'BODY') {
parentHeight = window.innerHeight;
}
_.context.clearRect(0, 0, element.width, element.height);
_.context.beginPath();
for (var i = storage.length; i--;) {
var particle = storage[i];
if (showParticles) {
particle._draw();
}
particle._updateCoordinates(parentWidth, parentHeight);
}
if (_.options.connectParticles) {
storage.sort(particleCompareFunc);
_._updateEdges();
}
};
/**
* Updates the edges.
*
* @private
*/
Plugin.prototype._updateEdges = function () {
var _ = this,
minDistance = _.options.minDistance,
sqrt = Math.sqrt,
abs = Math.abs,
storage = _.storage,
storageLength = storage.length;
for (var i = 0; i < storageLength; i++) {
var p1 = storage[i];
for (var j = i + 1; j < storageLength; j++) {
var p2 = storage[j],
distance, r = p1.x - p2.x, dy = p1.y - p2.y;
distance = sqrt(r * r + dy * dy);
if (abs(r) > minDistance) {
break;
}
if (distance <= minDistance) {
_._drawEdge(p1, p2, (1.2 - distance / minDistance));
}
}
}
};
/**
* Draws an edge between two points.
*
* @private
* @param {Particle} p1
* @param {Particle} p2
* @param {number} opacity
*/
Plugin.prototype._drawEdge = function (p1, p2, opacity) {
var _ = this,
gradient = _.context.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
var color1 = this._hex2rgb(p1.color);
var color2 = this._hex2rgb(p2.color);
gradient.addColorStop(0, 'rgba(' + color1.r + ',' + color1.g + ',' + color1.b + ',' + opacity + ')');
gradient.addColorStop(1, 'rgba(' + color2.r + ',' + color2.g + ',' + color2.b + ',' + opacity + ')');
_.context.beginPath();
_.context.strokeStyle = gradient;
_.context.moveTo(p1.x, p1.y);
_.context.lineTo(p2.x, p2.y);
_.context.stroke();
_.context.fill();
_.context.closePath();
};
/**
* Merges the keys of two objects.
*
* @private
* @param {object} source
* @param {object} obj
*/
Plugin.prototype._extend = function (source, obj) {
Object.keys(obj).forEach(function (key) {
source[key] = obj[key];
});
return source;
};
/**
* Converts a hex string to a rgb object.
*
* @private
* @param {string} hex
* @return {object}
*/
Plugin.prototype._hex2rgb = function (hex) {
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;
};
/**
* Represents a single particle.
*
* @constructor
* @param {object} context
* @param {object} options
*/
Particle = function (context, options) {
var _ = this,
random = Math.random,
speed = options.speed,
color = (options.color instanceof Array) ? options.color[Math.floor(Math.random() * options.color.length)] : options.color;
_.context = context;
_.options = options;
var canvas = document.querySelector(options.selector);
_.x = (canvas.offsetParent) ? random() * canvas.offsetParent.clientWidth : random() * canvas.clientWidth;
if (canvas.offsetParent && canvas.offsetParent.nodeName === 'BODY') {
_.y = random() * window.innerHeight;
} else {
_.y = (canvas.offsetParent) ? random() * canvas.offsetParent.clientHeight : random() * canvas.clientHeight;
}
_.vx = random() * speed * 2 - speed;
_.vy = random() * speed * 2 - speed;
_.radius = random() * random() * options.sizeVariations;
_.color = color;
_._draw();
};
/**
* The particles draw function (renders the circle).
*
* @private
*/
Particle.prototype._draw = function () {
var _ = this;
_.context.save();
_.context.translate(_.x, _.y);
_.context.moveTo(0, 0);
_.context.beginPath();
_.context.arc(0, 0, _.radius, 0, Math.PI * 2, false);
_.context.fillStyle = _.color;
_.context.fill();
_.context.restore();
};
/**
* This updates the particles coordinates.
*
* @private
* @param parentWidth
* @param parentHeight
*/
Particle.prototype._updateCoordinates = function (parentWidth, parentHeight) {
var _ = this,
x = _.x + this.vx,
y = _.y + this.vy,
radius = _.radius;
if (x + radius > parentWidth) {
x = radius;
} else if (x - radius < 0) {
x = parentWidth - radius;
}
if (y + radius > parentHeight) {
y = radius;
} else if (y - radius < 0) {
y = parentHeight - radius;
}
_.x = x;
_.y = y;
};
/**
* A polyfill for requestAnimFrame.
*
* @return {function}
*/
window.requestAnimFrame = (function () {
var _ = this,
requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
if (requestAnimationFrame) {
return requestAnimationFrame;
}
_._usingPolyfill = true;
return function (callback) {
return window.setTimeout(callback, 1000 / 60);
};
})();
return new Plugin();
})(window, document);
(function () {
'use strict';
if (typeof define === 'function' && define.amd) {
define('Particles', function () { return Particles; });
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = Particles;
} else {
window.Particles = Particles;
}
})();