diff --git a/src/components/misc/FancyBackground.tsx b/src/components/misc/FancyBackground.tsx new file mode 100644 index 0000000..c6f4a99 --- /dev/null +++ b/src/components/misc/FancyBackground.tsx @@ -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 ( +
+

0

+

0

+

0

+

0

+
+ ); +} + +export default FancyBackground;