|
|
|
/**
|
|
|
|
* Particle Network Animation https://codepen.io/franky/pen/LGMWPK
|
|
|
|
* Inspiration: https://github.com/JulianLaval/canvas-particle-network
|
|
|
|
*/
|
|
|
|
|
|
|
|
class ParticleNetworkAnimation {
|
|
|
|
constructor(element, options) {
|
|
|
|
this.$el = element;
|
|
|
|
|
|
|
|
this.container = element;
|
|
|
|
//this.canvas = document.createElement('canvas');
|
|
|
|
this.canvas = element;
|
|
|
|
this.sizeCanvas();
|
|
|
|
//this.container.appendChild(this.canvas);
|
|
|
|
this.ctx = this.canvas.getContext('2d');
|
|
|
|
this.particleNetwork = new ParticleNetwork(this, options);
|
|
|
|
|
|
|
|
this.bindUiActions();
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
bindUiActions() {
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
this.forceUpdate();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
sizeCanvas() {
|
|
|
|
this.canvas.width = this.container.offsetWidth;
|
|
|
|
this.canvas.height = this.container.offsetHeight;
|
|
|
|
}
|
|
|
|
forceUpdate() {
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
this.sizeCanvas();
|
|
|
|
this.particleNetwork.createParticles();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Particle{
|
|
|
|
constructor(parent, x, y) {
|
|
|
|
this.network = parent;
|
|
|
|
this.canvas = parent.canvas;
|
|
|
|
this.ctx = parent.ctx;
|
|
|
|
this.particleColor = returnRandomArrayitem(this.network.options.particleColors);
|
|
|
|
this.radius = getLimitedRandom(1.5, 2.5);
|
|
|
|
this.opacity = 0;
|
|
|
|
this.x = x || Math.random() * this.canvas.width;
|
|
|
|
this.y = y || Math.random() * this.canvas.height;
|
|
|
|
this.velocity = {
|
|
|
|
x: (Math.random() - 0.5) * parent.options.velocity,
|
|
|
|
y: (Math.random() - 0.5) * parent.options.velocity
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
update(){
|
|
|
|
if (this.opacity < 1) {
|
|
|
|
this.opacity += 0.01;
|
|
|
|
} else {
|
|
|
|
this.opacity = 1;
|
|
|
|
}
|
|
|
|
// Change dir if outside map
|
|
|
|
if (this.x > this.canvas.width + 100 || this.x < -100) {
|
|
|
|
this.velocity.x = -this.velocity.x;
|
|
|
|
}
|
|
|
|
if (this.y > this.canvas.height + 100 || this.y < -100) {
|
|
|
|
this.velocity.y = -this.velocity.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update position
|
|
|
|
this.x += this.velocity.x;
|
|
|
|
this.y += this.velocity.y;
|
|
|
|
}
|
|
|
|
draw(){
|
|
|
|
// Draw particle
|
|
|
|
this.ctx.beginPath();
|
|
|
|
this.ctx.fillStyle = this.particleColor;
|
|
|
|
this.ctx.globalAlpha = this.opacity;
|
|
|
|
this.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
|
|
|
|
this.ctx.fill();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class ParticleNetwork{
|
|
|
|
constructor(parent, options = {
|
|
|
|
velocity: 2, // the higher the faster
|
|
|
|
density: 10000, // the lower the denser
|
|
|
|
netLineDistance: 200,
|
|
|
|
netLineColor: '#929292',
|
|
|
|
particleColors: ['#aaa'] // ['#6D4E5C', '#aaa', '#FFC458' ]
|
|
|
|
}) {
|
|
|
|
this.options = options;
|
|
|
|
this.canvas = parent.canvas;
|
|
|
|
this.ctx = parent.ctx;
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
}
|
|
|
|
|
|
|
|
init(){
|
|
|
|
// Create particle objects
|
|
|
|
this.createParticles(false);
|
|
|
|
|
|
|
|
// Update canvas
|
|
|
|
this.animationFrame = requestAnimationFrame(this.update.bind(this));
|
|
|
|
|
|
|
|
this.bindUiActions();
|
|
|
|
}
|
|
|
|
|
|
|
|
createParticles(isInitial) {
|
|
|
|
// Initialise / reset particles
|
|
|
|
let me = this;
|
|
|
|
this.particles = [];
|
|
|
|
let quantity = this.canvas.width * this.canvas.height / this.options.density;
|
|
|
|
|
|
|
|
if (isInitial) {
|
|
|
|
let counter = 0;
|
|
|
|
clearInterval(this.createIntervalId);
|
|
|
|
this.createIntervalId = setInterval(function() {
|
|
|
|
if (counter < quantity - 1) {
|
|
|
|
// Create particle object
|
|
|
|
this.particles.push(new Particle(this));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clearInterval(me.createIntervalId);
|
|
|
|
}
|
|
|
|
counter++;
|
|
|
|
}.bind(this), 250);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Create particle objects
|
|
|
|
for (let i = 0; i < quantity; i++) {
|
|
|
|
this.particles.push(new Particle(this));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
createInteractionParticle() {
|
|
|
|
// Add interaction particle
|
|
|
|
this.interactionParticle = new Particle(this);
|
|
|
|
this.interactionParticle.velocity = {
|
|
|
|
x: 0,
|
|
|
|
y: 0
|
|
|
|
};
|
|
|
|
this.particles.push(this.interactionParticle);
|
|
|
|
return this.interactionParticle;
|
|
|
|
}
|
|
|
|
|
|
|
|
removeInteractionParticle() {
|
|
|
|
// Find it
|
|
|
|
let index = this.particles.indexOf(this.interactionParticle);
|
|
|
|
if (index > -1) {
|
|
|
|
// Remove it
|
|
|
|
this.interactionParticle = undefined;
|
|
|
|
this.particles.splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
update() {
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
this.ctx.globalAlpha = 1;
|
|
|
|
|
|
|
|
// Draw connections
|
|
|
|
for (let i = 0; i < this.particles.length; i++) {
|
|
|
|
for (let j = this.particles.length - 1; j > i; j--) {
|
|
|
|
let distance, p1 = this.particles[i], p2 = this.particles[j];
|
|
|
|
|
|
|
|
// check very simply if the two points are even a candidate for further measurements
|
|
|
|
distance = Math.min(Math.abs(p1.x - p2.x), Math.abs(p1.y - p2.y));
|
|
|
|
if (distance > this.options.netLineDistance) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the two points seem close enough, now let's measure precisely
|
|
|
|
distance = Math.sqrt(
|
|
|
|
Math.pow(p1.x - p2.x, 2) +
|
|
|
|
Math.pow(p1.y - p2.y, 2)
|
|
|
|
);
|
|
|
|
if (distance > this.options.netLineDistance) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.ctx.beginPath();
|
|
|
|
this.ctx.strokeStyle = this.options.netLineColor;
|
|
|
|
this.ctx.globalAlpha = (this.options.netLineDistance - distance) / this.options.netLineDistance * p1.opacity * p2.opacity;
|
|
|
|
this.ctx.lineWidth = 0.7;
|
|
|
|
this.ctx.moveTo(p1.x, p1.y);
|
|
|
|
this.ctx.lineTo(p2.x, p2.y);
|
|
|
|
this.ctx.stroke();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw particles
|
|
|
|
this.particles.forEach(k => {
|
|
|
|
k.update();
|
|
|
|
k.draw();
|
|
|
|
})
|
|
|
|
|
|
|
|
if (this.options.velocity !== 0) {
|
|
|
|
this.animationFrame = requestAnimationFrame(this.update.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
cancelAnimationFrame(this.animationFrame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bindUiActions() {
|
|
|
|
// Mouse / touch event handling
|
|
|
|
this.spawnQuantity = 3;
|
|
|
|
this.mouseIsDown = false;
|
|
|
|
this.touchIsMoving = false;
|
|
|
|
|
|
|
|
this.onMouseMove = function(e) {
|
|
|
|
if (!this.interactionParticle) {
|
|
|
|
this.createInteractionParticle();
|
|
|
|
}
|
|
|
|
this.interactionParticle.x = e.offsetX;
|
|
|
|
this.interactionParticle.y = e.offsetY;
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.onTouchMove = function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
this.touchIsMoving = true;
|
|
|
|
if (!this.interactionParticle) {
|
|
|
|
this.createInteractionParticle();
|
|
|
|
}
|
|
|
|
this.interactionParticle.x = e.changedTouches[0].clientX;
|
|
|
|
this.interactionParticle.y = e.changedTouches[0].clientY;
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.onMouseDown = function() {
|
|
|
|
this.mouseIsDown = true;
|
|
|
|
let counter = 0;
|
|
|
|
let quantity = this.spawnQuantity;
|
|
|
|
let intervalId = setInterval(function() {
|
|
|
|
if (this.mouseIsDown) {
|
|
|
|
if (counter === 1) {
|
|
|
|
quantity = 1;
|
|
|
|
}
|
|
|
|
for (let i = 0; i < quantity; i++) {
|
|
|
|
if (this.interactionParticle) {
|
|
|
|
this.particles.push(new Particle(this, this.interactionParticle.x, this.interactionParticle.y));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clearInterval(intervalId);
|
|
|
|
}
|
|
|
|
counter++;
|
|
|
|
}.bind(this), 50);
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.onTouchStart = function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
setTimeout(function() {
|
|
|
|
if (!this.touchIsMoving) {
|
|
|
|
for (let i = 0; i < this.spawnQuantity; i++) {
|
|
|
|
this.particles.push(new Particle(this, e.changedTouches[0].clientX, e.changedTouches[0].clientY));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.bind(this), 200);
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.onMouseUp = function() {
|
|
|
|
this.mouseIsDown = false;
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.onMouseOut = function() {
|
|
|
|
this.removeInteractionParticle();
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.onTouchEnd = function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
this.touchIsMoving = false;
|
|
|
|
this.removeInteractionParticle();
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.canvas.addEventListener('mousemove', this.onMouseMove);
|
|
|
|
this.canvas.addEventListener('touchmove', this.onTouchMove);
|
|
|
|
//this.canvas.addEventListener('mousedown', this.onMouseDown);
|
|
|
|
this.canvas.addEventListener('touchstart', this.onTouchStart);
|
|
|
|
this.canvas.addEventListener('mouseup', this.onMouseUp);
|
|
|
|
this.canvas.addEventListener('mouseout', this.onMouseOut);
|
|
|
|
this.canvas.addEventListener('touchend', this.onTouchEnd);
|
|
|
|
}
|
|
|
|
|
|
|
|
unbindUiActions() {
|
|
|
|
if (this.canvas) {
|
|
|
|
this.canvas.removeEventListener('mousemove', this.onMouseMove);
|
|
|
|
this.canvas.removeEventListener('touchmove', this.onTouchMove);
|
|
|
|
this.canvas.removeEventListener('mousedown', this.onMouseDown);
|
|
|
|
this.canvas.removeEventListener('touchstart', this.onTouchStart);
|
|
|
|
this.canvas.removeEventListener('mouseup', this.onMouseUp);
|
|
|
|
this.canvas.removeEventListener('mouseout', this.onMouseOut);
|
|
|
|
this.canvas.removeEventListener('touchend', this.onTouchEnd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let getLimitedRandom = function(min, max, roundToInteger) {
|
|
|
|
let number = Math.random() * (max - min) + min;
|
|
|
|
if (roundToInteger) {
|
|
|
|
number = Math.round(number);
|
|
|
|
}
|
|
|
|
return number;
|
|
|
|
};
|
|
|
|
|
|
|
|
let returnRandomArrayitem = function(array) {
|
|
|
|
return array[Math.floor(Math.random()*array.length)];
|
|
|
|
};
|
|
|
|
|
|
|
|
export {
|
|
|
|
ParticleNetworkAnimation
|
|
|
|
};
|