213 lines
5.5 KiB
TypeScript
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);
|
|
};
|
|
};
|