mirror of
https://github.com/Lissy93/web-check.git
synced 2025-06-19 19:28:00 +02:00
🦄 Adds a fancy animated background for homepage
This commit is contained in:
parent
11506277a7
commit
e351bc34bb
347
src/components/misc/FancyBackground.tsx
Normal file
347
src/components/misc/FancyBackground.tsx
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
|
||||||
|
|
||||||
|
const FancyBackground = (): JSX.Element => {
|
||||||
|
|
||||||
|
const makeAbsolute = (elem: HTMLElement) => {
|
||||||
|
elem.style.position = 'absolute';
|
||||||
|
elem.style.top = '0';
|
||||||
|
elem.style.left = '0';
|
||||||
|
};
|
||||||
|
|
||||||
|
const maxBy = (array: any) => {
|
||||||
|
const chaos = 30;
|
||||||
|
const iteratee = (e: any) => e.field + chaos * Math.random();
|
||||||
|
let result;
|
||||||
|
if (array == null) { return result; }
|
||||||
|
let computed;
|
||||||
|
for (const value of array) {
|
||||||
|
const current = iteratee(value);
|
||||||
|
if (current != null && (computed === undefined ? current : current > computed)) {
|
||||||
|
computed = current;
|
||||||
|
result = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const App: any = {};
|
||||||
|
|
||||||
|
App.setup = function () {
|
||||||
|
|
||||||
|
this.lifespan = 1000;
|
||||||
|
this.popPerBirth = 1;
|
||||||
|
this.maxPop = 300;
|
||||||
|
this.birthFreq = 2;
|
||||||
|
this.bgColor = '#141d2b';
|
||||||
|
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
canvas.style.opacity = '0.5';
|
||||||
|
makeAbsolute(canvas);
|
||||||
|
this.canvas = canvas;
|
||||||
|
const container = document.getElementById('fancy-background');
|
||||||
|
if (container) {
|
||||||
|
container.style.color = this.bgColor;
|
||||||
|
makeAbsolute(container);
|
||||||
|
container.appendChild(canvas);
|
||||||
|
}
|
||||||
|
this.ctx = this.canvas.getContext('2d');
|
||||||
|
this.width = this.canvas.width;
|
||||||
|
this.height = this.canvas.height;
|
||||||
|
this.dataToImageRatio = 1;
|
||||||
|
this.ctx.imageSmoothingEnabled = false;
|
||||||
|
this.ctx.webkitImageSmoothingEnabled = false;
|
||||||
|
this.ctx.msImageSmoothingEnabled = false;
|
||||||
|
this.xC = this.width / 2;
|
||||||
|
this.yC = this.height / 2;
|
||||||
|
|
||||||
|
this.stepCount = 0;
|
||||||
|
this.particles = [];
|
||||||
|
|
||||||
|
|
||||||
|
// Build grid
|
||||||
|
this.gridSize = 8; // Motion coords
|
||||||
|
this.gridSteps = Math.floor(1000 / this.gridSize);
|
||||||
|
this.grid = [];
|
||||||
|
var i = 0;
|
||||||
|
for (var xx = -500; xx < 500; xx += this.gridSize) {
|
||||||
|
for (var yy = -500; yy < 500; yy += this.gridSize) {
|
||||||
|
// Radial field, triangular function of r with max around r0
|
||||||
|
var r = Math.sqrt(xx * xx + yy * yy),
|
||||||
|
r0 = 100,
|
||||||
|
field;
|
||||||
|
|
||||||
|
if (r < r0) field = (255 / r0) * r;
|
||||||
|
else if (r > r0) field = 255 - Math.min(255, (r - r0) / 2);
|
||||||
|
|
||||||
|
this.grid.push({
|
||||||
|
x: xx,
|
||||||
|
y: yy,
|
||||||
|
busyAge: 0,
|
||||||
|
spotIndex: i,
|
||||||
|
isEdge:
|
||||||
|
xx === -500
|
||||||
|
? 'left'
|
||||||
|
: xx === -500 + this.gridSize * (this.gridSteps - 1)
|
||||||
|
? 'right'
|
||||||
|
: yy === -500
|
||||||
|
? 'top'
|
||||||
|
: yy === -500 + this.gridSize * (this.gridSteps - 1)
|
||||||
|
? 'bottom'
|
||||||
|
: false,
|
||||||
|
field: field,
|
||||||
|
});
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.gridMaxIndex = i;
|
||||||
|
|
||||||
|
// Counters for UI
|
||||||
|
this.drawnInLastFrame = 0;
|
||||||
|
this.deathCount = 0;
|
||||||
|
|
||||||
|
this.initDraw();
|
||||||
|
};
|
||||||
|
App.evolve = function () {
|
||||||
|
var time1 = performance.now();
|
||||||
|
|
||||||
|
this.stepCount++;
|
||||||
|
|
||||||
|
// Increment all grid ages
|
||||||
|
this.grid.forEach(function (e: any) {
|
||||||
|
if (e.busyAge > 0) e.busyAge++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.stepCount % this.birthFreq === 0 &&
|
||||||
|
this.particles.length + this.popPerBirth < this.maxPop
|
||||||
|
) {
|
||||||
|
this.birth();
|
||||||
|
}
|
||||||
|
App.move();
|
||||||
|
App.draw();
|
||||||
|
|
||||||
|
var time2 = performance.now();
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
document.getElementsByClassName('dead')[0].textContent = this.deathCount;
|
||||||
|
document.getElementsByClassName('alive')[0].textContent =
|
||||||
|
this.particles.length;
|
||||||
|
document.getElementsByClassName('fps')[0].textContent = Math.floor(
|
||||||
|
1000 / (time2 - time1)
|
||||||
|
).toString();
|
||||||
|
document.getElementsByClassName('drawn')[0].textContent =
|
||||||
|
this.drawnInLastFrame;
|
||||||
|
};
|
||||||
|
App.birth = function () {
|
||||||
|
var x, y;
|
||||||
|
var gridSpotIndex = Math.floor(Math.random() * this.gridMaxIndex);
|
||||||
|
var gridSpot = this.grid[gridSpotIndex];
|
||||||
|
x = gridSpot.x;
|
||||||
|
y = gridSpot.y;
|
||||||
|
|
||||||
|
var particle = {
|
||||||
|
hue: 200, // + Math.floor(50*Math.random()),
|
||||||
|
sat: 95, //30 + Math.floor(70*Math.random()),
|
||||||
|
lum: 20 + Math.floor(40 * Math.random()),
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
xLast: x,
|
||||||
|
yLast: y,
|
||||||
|
xSpeed: 0,
|
||||||
|
ySpeed: 0,
|
||||||
|
age: 0,
|
||||||
|
ageSinceStuck: 0,
|
||||||
|
attractor: {
|
||||||
|
oldIndex: gridSpotIndex,
|
||||||
|
gridSpotIndex: gridSpotIndex, // Pop at random position on grid
|
||||||
|
},
|
||||||
|
name: 'seed-' + Math.ceil(10000000 * Math.random()),
|
||||||
|
};
|
||||||
|
this.particles.push(particle);
|
||||||
|
};
|
||||||
|
App.kill = function (particleName: any) {
|
||||||
|
const newArray = this.particles.filter(
|
||||||
|
(seed: any) => seed.name !== particleName
|
||||||
|
);
|
||||||
|
this.particles = [...newArray];
|
||||||
|
};
|
||||||
|
App.move = function () {
|
||||||
|
for (var i = 0; i < this.particles.length; i++) {
|
||||||
|
// Get particle
|
||||||
|
var p = this.particles[i];
|
||||||
|
|
||||||
|
// Save last position
|
||||||
|
p.xLast = p.x;
|
||||||
|
p.yLast = p.y;
|
||||||
|
|
||||||
|
// Attractor and corresponding grid spot
|
||||||
|
var index = p.attractor.gridSpotIndex,
|
||||||
|
gridSpot = this.grid[index];
|
||||||
|
|
||||||
|
// Maybe move attractor and with certain constraints
|
||||||
|
if (Math.random() < 0.5) {
|
||||||
|
// Move attractor
|
||||||
|
if (!gridSpot.isEdge) {
|
||||||
|
// Change particle's attractor grid spot and local move function's grid spot
|
||||||
|
var topIndex = index - 1,
|
||||||
|
bottomIndex = index + 1,
|
||||||
|
leftIndex = index - this.gridSteps,
|
||||||
|
rightIndex = index + this.gridSteps,
|
||||||
|
topSpot = this.grid[topIndex],
|
||||||
|
bottomSpot = this.grid[bottomIndex],
|
||||||
|
leftSpot = this.grid[leftIndex],
|
||||||
|
rightSpot = this.grid[rightIndex];
|
||||||
|
|
||||||
|
var maxFieldSpot = maxBy(
|
||||||
|
[topSpot, bottomSpot, leftSpot, rightSpot]
|
||||||
|
);
|
||||||
|
|
||||||
|
var potentialNewGridSpot = maxFieldSpot;
|
||||||
|
if (
|
||||||
|
potentialNewGridSpot.busyAge === 0 ||
|
||||||
|
potentialNewGridSpot.busyAge > 15
|
||||||
|
) {
|
||||||
|
p.ageSinceStuck = 0;
|
||||||
|
p.attractor.oldIndex = index;
|
||||||
|
p.attractor.gridSpotIndex = potentialNewGridSpot.spotIndex;
|
||||||
|
gridSpot = potentialNewGridSpot;
|
||||||
|
gridSpot.busyAge = 1;
|
||||||
|
} else p.ageSinceStuck++;
|
||||||
|
} else p.ageSinceStuck++;
|
||||||
|
|
||||||
|
if (p.ageSinceStuck === 10) this.kill(p.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spring attractor to center with viscosity
|
||||||
|
const k = 8, visc = 0.4;
|
||||||
|
var dx = p.x - gridSpot.x,
|
||||||
|
dy = p.y - gridSpot.y,
|
||||||
|
dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// Spring
|
||||||
|
var xAcc = -k * dx,
|
||||||
|
yAcc = -k * dy;
|
||||||
|
|
||||||
|
p.xSpeed += xAcc;
|
||||||
|
p.ySpeed += yAcc;
|
||||||
|
|
||||||
|
// Calm the f*ck down
|
||||||
|
p.xSpeed *= visc;
|
||||||
|
p.ySpeed *= visc;
|
||||||
|
|
||||||
|
// Store stuff in particle brain
|
||||||
|
p.speed = Math.sqrt(p.xSpeed * p.xSpeed + p.ySpeed * p.ySpeed);
|
||||||
|
p.dist = dist;
|
||||||
|
|
||||||
|
// Update position
|
||||||
|
p.x += 0.1 * p.xSpeed;
|
||||||
|
p.y += 0.1 * p.ySpeed;
|
||||||
|
|
||||||
|
// Get older
|
||||||
|
p.age++;
|
||||||
|
|
||||||
|
// Kill if too old
|
||||||
|
if (p.age > this.lifespan) {
|
||||||
|
this.kill(p.name);
|
||||||
|
this.deathCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
App.initDraw = function () {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.rect(0, 0, this.width, this.height);
|
||||||
|
this.ctx.fillStyle = this.bgColor;
|
||||||
|
this.ctx.fill();
|
||||||
|
this.ctx.closePath();
|
||||||
|
};
|
||||||
|
App.draw = function () {
|
||||||
|
this.drawnInLastFrame = 0;
|
||||||
|
if (!this.particles.length) return false;
|
||||||
|
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.rect(0, 0, this.width, this.height);
|
||||||
|
this.ctx.fillStyle = this.bgColor;
|
||||||
|
this.ctx.fill();
|
||||||
|
this.ctx.closePath();
|
||||||
|
|
||||||
|
for (var i = 0; i < this.particles.length; i++) {
|
||||||
|
var p = this.particles[i];
|
||||||
|
|
||||||
|
var last = this.dataXYtoCanvasXY(p.xLast, p.yLast),
|
||||||
|
now = this.dataXYtoCanvasXY(p.x, p.y);
|
||||||
|
var attracSpot = this.grid[p.attractor.gridSpotIndex],
|
||||||
|
attracXY = this.dataXYtoCanvasXY(attracSpot.x, attracSpot.y);
|
||||||
|
var oldAttracSpot = this.grid[p.attractor.oldIndex],
|
||||||
|
oldAttracXY = this.dataXYtoCanvasXY(oldAttracSpot.x, oldAttracSpot.y);
|
||||||
|
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.strokeStyle = '#9fef00';
|
||||||
|
this.ctx.fillStyle = '#9fef00';
|
||||||
|
|
||||||
|
// Particle trail
|
||||||
|
this.ctx.moveTo(last.x, last.y);
|
||||||
|
this.ctx.lineTo(now.x, now.y);
|
||||||
|
|
||||||
|
this.ctx.lineWidth = 1.5 * this.dataToImageRatio;
|
||||||
|
this.ctx.stroke();
|
||||||
|
this.ctx.closePath();
|
||||||
|
|
||||||
|
// Attractor positions
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.lineWidth = 1.5 * this.dataToImageRatio;
|
||||||
|
this.ctx.moveTo(oldAttracXY.x, oldAttracXY.y);
|
||||||
|
this.ctx.lineTo(attracXY.x, attracXY.y);
|
||||||
|
this.ctx.arc(
|
||||||
|
attracXY.x,
|
||||||
|
attracXY.y,
|
||||||
|
1.5 * this.dataToImageRatio,
|
||||||
|
0,
|
||||||
|
2 * Math.PI,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
this.ctx.strokeStyle = '#9fef00';
|
||||||
|
this.ctx.fillStyle = '#9fef00';
|
||||||
|
|
||||||
|
this.ctx.stroke();
|
||||||
|
this.ctx.fill();
|
||||||
|
|
||||||
|
this.ctx.closePath();
|
||||||
|
|
||||||
|
// UI counter
|
||||||
|
this.drawnInLastFrame++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
App.dataXYtoCanvasXY = function (x: number, y: number) {
|
||||||
|
var zoom = 1.6;
|
||||||
|
var xx = this.xC + x * zoom * this.dataToImageRatio,
|
||||||
|
yy = this.yC + y * zoom * this.dataToImageRatio;
|
||||||
|
|
||||||
|
return { x: xx, y: yy };
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
App.setup();
|
||||||
|
App.draw();
|
||||||
|
|
||||||
|
var frame = function () {
|
||||||
|
App.evolve();
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
};
|
||||||
|
frame();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='ui' id='fancy-background'>
|
||||||
|
<p><span className='dead'>0</span></p>
|
||||||
|
<p><span className='alive'>0</span></p>
|
||||||
|
<p><span className='drawn'>0</span></p>
|
||||||
|
<p><span className='fps'>0</span></p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FancyBackground;
|
Loading…
x
Reference in New Issue
Block a user