sitegen/src/file-viewer/scripts/canvas_2019.ts

213 lines
5.5 KiB
TypeScript

// Ported from CanvasAPI, allegedly written on 2019-08-26.
(globalThis as any).canvas_2019 = function (canvas: HTMLCanvasElement) {
const isStandalone = canvas.getAttribute("data-standalone") === "true";
if (isStandalone) {
canvas.parentElement!.style.backgroundColor = "#121013";
}
// Canvas.tsx
abstract class CanvasAPI {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
width = 0;
height = 0;
private _disposed = false;
private _running = false;
private _last = 0;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.width = canvas.width = canvas.clientWidth;
this.height = canvas.height = canvas.clientHeight;
const ctx = this.canvas.getContext("2d");
if (!ctx) {
throw new Error("Canvas2D Not Supported!");
}
this.ctx = ctx;
}
stopRenderLoop() {
this._running = false;
}
startRenderLoop() {
if (this._disposed) return;
this._running = true;
this._last = performance.now();
requestAnimationFrame(this._renderLoop);
}
private _renderLoop = (delta: number) => {
if (!this._running) return;
this.render(delta - this._last);
this._last = delta;
requestAnimationFrame(this._renderLoop);
};
abstract render(delta: number): void;
}
// VaultBackground.ts
function addGSHelper(
grad: CanvasGradient,
color: string,
dotOpacity: number,
gradStop: number,
gradOpacity: number,
) {
grad.addColorStop(gradStop, `rgba(${color},${dotOpacity * gradOpacity})`);
}
function randAround(target: number, dist: number) {
return (Math.random() - 0.5) * dist * 2 + target;
}
class Dot {
x = Math.random() * 1.1 - 0.05;
y = Math.random() / 4 + 0.9;
size = Math.random() * 200 + 50;
opacity = 0;
opacityRandom = Math.random() / 3 + 0.3;
fadeInOpacity = 1;
color = `${randAround(217, 30)}, ${randAround(170, 30)}, ${
randAround(255, 20)
}`;
life = 0;
ySpeed_1 = 0;
ySpeed_2 = -0.0000063;
ySpeed_3 = 0.000000016;
ySpeed_4 = 0.000000000009;
seed = Math.random();
delete = false;
update(init: boolean) {
this.life += 0.8;
if (this.life < 115) {
this.opacity = this.life / 230;
} else if (this.life > 450) {
this.delete = true;
} else if (this.life > 300) {
this.opacity = (150 + 300 - this.life) / 300;
}
this.ySpeed_3 += this.ySpeed_4;
this.ySpeed_2 += this.ySpeed_3;
this.ySpeed_1 += this.ySpeed_2;
this.y += this.ySpeed_1 * 0.5;
this.size -= 0.08;
if (this.delete) {
Object.assign(this, new Dot());
}
}
render(scene: VaultBackground) {
const ctx = scene.ctx;
if (this.fadeInOpacity < 1) {
this.fadeInOpacity += 0.0075;
}
const finalX = this.x +
Math.sin(this.seed * Math.PI * 2 + Date.now() / 15000) * 0.2;
const drawX = scene.shakeX +
finalX * Math.max(700, scene.width) -
(Math.max(700, scene.width) - scene.width) / 2;
const drawY = scene.shakeY + (this.y * 1.5 - 0.5) * scene.height;
const opacity = this.opacity * this.opacityRandom * this.fadeInOpacity;
const grad = ctx.createRadialGradient(
drawX,
drawY,
0,
drawX,
drawY,
this.size,
);
addGSHelper(grad, this.color, opacity, 0, 1);
addGSHelper(grad, this.color, opacity, 0.8, 0.7);
addGSHelper(grad, this.color, opacity, 0.87, 0.5);
addGSHelper(grad, this.color, opacity, 0.93, 0.3);
addGSHelper(grad, this.color, opacity, 1, 0);
ctx.fillStyle = grad;
ctx.fillRect(
drawX - this.size,
drawY - this.size,
this.size * 2,
this.size * 2,
);
}
}
class VaultBackground extends CanvasAPI {
private items = new Set<Dot>();
private shakeVar = 0;
private dom?: HTMLElement;
shakeX = 0;
shakeY = 0;
constructor(canvas: HTMLCanvasElement) {
super(canvas);
for (let i = 0; i < 450; i++) {
if (i % 7 === 0) {
this.items.add(new Dot());
}
this.items.forEach((x) => x.update(true));
}
this.items.forEach((x) => x.fadeInOpacity = 0);
}
render(): void {
this.ctx.clearRect(0, 0, this.width, this.height);
this.items.forEach((x) => (x.update(false), x.render(this)));
if (this.shakeVar >= 0.0001) {
this.shakeVar *= 0.97 - 0.22 * this.shakeVar;
if (this.shakeVar >= 0.0001) {
this.shakeX = (Math.random() * 2 - 1) * this.shakeVar * 65;
this.shakeY = (Math.random() * 2 - 1) * this.shakeVar * 65;
if (this.dom) {
this.dom.style.transform =
`translate(${this.shakeX}px,${this.shakeY}px)`;
}
} else {
this.shakeX = 0;
this.shakeY = 0;
if (this.dom) this.dom.style.removeProperty("transform");
this.dom = undefined;
}
}
}
shake(dom?: HTMLElement | null) {
this.dom = dom || document.body;
this.shakeVar = 1;
}
}
// Binding code
let bg = new VaultBackground(canvas);
bg.startRenderLoop();
canvas.style.opacity = "0.2";
function onResize() {
bg.width = canvas.width = canvas.clientWidth;
bg.height = canvas.height = canvas.clientHeight;
}
window.addEventListener("resize", onResize);
onResize();
(globalThis as any).vault = bg;
return () => {
bg.stopRenderLoop();
window.removeEventListener("resize", onResize);
};
};