mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 20:34:38 +00:00
176 lines
4.4 KiB
JavaScript
176 lines
4.4 KiB
JavaScript
const NS = "http://www.w3.org/2000/svg";
|
|
const $svg = $("#canvas");
|
|
|
|
const CONFIG = {
|
|
holeRadius: 60,
|
|
maxRadius: 220,
|
|
tilt: 0.4,
|
|
|
|
ringsCount: 7,
|
|
ringSpeed: 0.002,
|
|
ringStroke: 5,
|
|
|
|
particlesCount: 40,
|
|
particleSpeedBase: 0.02,
|
|
particleFallSpeed: 0.2,
|
|
};
|
|
|
|
function create(tag, attrs) {
|
|
const el = document.createElementNS(NS, tag);
|
|
for (let k in attrs) el.setAttribute(k, attrs[k]);
|
|
return el;
|
|
}
|
|
|
|
const $layerBack = $(create("g", { id: "layer-back" }));
|
|
const $layerHole = $(create("g", { id: "layer-hole" }));
|
|
const $layerFront = $(create("g", { id: "layer-front" }));
|
|
|
|
$svg.append($layerBack, $layerHole, $layerFront);
|
|
|
|
const holeHalo = create("circle", {
|
|
cx: 0,
|
|
cy: 0,
|
|
r: CONFIG.holeRadius + 4,
|
|
fill: "#ffffff",
|
|
stroke: "none",
|
|
});
|
|
const holeBody = create("circle", {
|
|
cx: 0,
|
|
cy: 0,
|
|
r: CONFIG.holeRadius,
|
|
fill: "#000000",
|
|
});
|
|
$layerHole.append(holeHalo, holeBody);
|
|
|
|
class Ring {
|
|
constructor(offset) {
|
|
this.progress = offset;
|
|
|
|
const style = {
|
|
fill: "none",
|
|
stroke: "#000",
|
|
"stroke-linecap": "round",
|
|
"stroke-width": CONFIG.ringStroke,
|
|
};
|
|
|
|
this.elBack = create("path", style);
|
|
this.elFront = create("path", style);
|
|
|
|
$layerBack.append(this.elBack);
|
|
$layerFront.append(this.elFront);
|
|
}
|
|
|
|
update() {
|
|
this.progress += CONFIG.ringSpeed;
|
|
if (this.progress >= 1) this.progress -= 1;
|
|
|
|
const t = this.progress;
|
|
|
|
const currentR =
|
|
CONFIG.maxRadius - t * (CONFIG.maxRadius - CONFIG.holeRadius);
|
|
const currentRy = currentR * CONFIG.tilt;
|
|
|
|
const distFromHole = currentR - CONFIG.holeRadius;
|
|
const distFromEdge = CONFIG.maxRadius - currentR;
|
|
|
|
const fadeHole = Math.min(1, distFromHole / 40);
|
|
const fadeEdge = Math.min(1, distFromEdge / 40);
|
|
|
|
const opacity = fadeHole * fadeEdge;
|
|
|
|
if (opacity <= 0.01) {
|
|
this.elBack.setAttribute("opacity", 0);
|
|
this.elFront.setAttribute("opacity", 0);
|
|
} else {
|
|
const dBack = `M ${-currentR} 0 A ${currentR} ${currentRy} 0 0 1 ${currentR} 0`;
|
|
const dFront = `M ${-currentR} 0 A ${currentR} ${currentRy} 0 0 0 ${currentR} 0`;
|
|
|
|
this.elBack.setAttribute("d", dBack);
|
|
this.elFront.setAttribute("d", dFront);
|
|
|
|
this.elBack.setAttribute("opacity", opacity);
|
|
this.elFront.setAttribute("opacity", opacity);
|
|
|
|
const sw =
|
|
CONFIG.ringStroke *
|
|
(0.6 + 0.4 * (distFromHole / (CONFIG.maxRadius - CONFIG.holeRadius)));
|
|
this.elBack.setAttribute("stroke-width", sw);
|
|
this.elFront.setAttribute("stroke-width", sw);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Particle {
|
|
constructor() {
|
|
this.el = create("circle", { fill: "#000" });
|
|
this.reset(true);
|
|
$layerFront.append(this.el);
|
|
this.inFront = true;
|
|
}
|
|
|
|
reset(randomStart = false) {
|
|
this.angle = Math.random() * Math.PI * 2;
|
|
this.r = randomStart
|
|
? CONFIG.holeRadius +
|
|
Math.random() * (CONFIG.maxRadius - CONFIG.holeRadius)
|
|
: CONFIG.maxRadius;
|
|
|
|
this.speed = CONFIG.particleSpeedBase + Math.random() * 0.02;
|
|
this.size = 1.5 + Math.random() * 2.5;
|
|
}
|
|
|
|
update() {
|
|
const acceleration = CONFIG.maxRadius / this.r;
|
|
this.angle += this.speed * acceleration;
|
|
this.r -= CONFIG.particleFallSpeed * (acceleration * 0.8);
|
|
|
|
const x = Math.cos(this.angle) * this.r;
|
|
const y = Math.sin(this.angle) * this.r * CONFIG.tilt;
|
|
|
|
const isNowFront = Math.sin(this.angle) > 0;
|
|
|
|
if (this.inFront !== isNowFront) {
|
|
this.inFront = isNowFront;
|
|
if (this.inFront) {
|
|
$layerFront.append(this.el);
|
|
} else {
|
|
$layerBack.append(this.el);
|
|
}
|
|
}
|
|
|
|
const distFromHole = this.r - CONFIG.holeRadius;
|
|
const distFromEdge = CONFIG.maxRadius - this.r;
|
|
|
|
const fadeHole = Math.min(1, distFromHole / 30);
|
|
const fadeEdge = Math.min(1, distFromEdge / 30);
|
|
const opacity = fadeHole * fadeEdge;
|
|
|
|
this.el.setAttribute("cx", x);
|
|
this.el.setAttribute("cy", y);
|
|
this.el.setAttribute("r", this.size * Math.min(1, this.r / 100));
|
|
this.el.setAttribute("opacity", opacity);
|
|
|
|
if (this.r <= CONFIG.holeRadius) {
|
|
this.reset(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
const rings = [];
|
|
for (let i = 0; i < CONFIG.ringsCount; i++) {
|
|
rings.push(new Ring(i / CONFIG.ringsCount));
|
|
}
|
|
|
|
const particles = [];
|
|
for (let i = 0; i < CONFIG.particlesCount; i++) {
|
|
particles.push(new Particle());
|
|
}
|
|
|
|
function animate() {
|
|
rings.forEach((r) => r.update());
|
|
particles.forEach((p) => p.update());
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|