Skip to content

Commit a9531ed

Browse files
committed
Update
1 parent 308588d commit a9531ed

4 files changed

Lines changed: 167 additions & 4 deletions

File tree

src/App.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ footer {
153153
/*noinspection CssUnknownProperty,CssInvalidFunction,CssInvalidPropertyValue*/
154154
.progress-bar-before {
155155
position: fixed;
156-
height: 5px;
156+
height: 2px;
157157
width: 100%;
158-
top: 4.25rem;
158+
top: 4.4rem;
159159
transform-origin: 0 50%;
160160
z-index: 15;
161161
animation: scaleProgress auto linear;

src/App.tsx

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import {Repos} from "./assets/Data.tsx";
1515
import HeroMainText from "./assets/HeroMainText.tsx";
1616
import Section from "./assets/Section.tsx";
17+
import {MainAnimationDecorator} from "./assets/MainAnimationDecorator.tsx";
1718

1819
export interface ProjectWrapper {
1920
name: string
@@ -59,14 +60,114 @@ interface Props {
5960
search: string
6061
}
6162

63+
declare global {
64+
interface Window {
65+
_particles?: Array<{
66+
sx: number;
67+
sy: number;
68+
x: number;
69+
y: number;
70+
vx: number;
71+
vy: number;
72+
trail: Array<{ x: number; y: number; alpha: number }>;
73+
}>;
74+
}
75+
}
76+
77+
const renderFunc = (ctx: CanvasRenderingContext2D, width: number, height: number) => {
78+
const cssVariable = getComputedStyle(document.documentElement).getPropertyValue('--p').trim();
79+
80+
if (!window._particles) {
81+
window._particles = [];
82+
83+
const numParticles = 100;
84+
85+
while (window._particles!.length < numParticles) {
86+
const sx = Math.random() * width;
87+
const sy = Math.random() * height;
88+
89+
window._particles!.push({
90+
x: sx,
91+
y: sy,
92+
sx: sx,
93+
sy: sy,
94+
vx: (0.15 + Math.random() * 0.1) * (width / 500),
95+
vy: (0.15 + Math.random() * 0.1) * (height / 500),
96+
trail: []
97+
});
98+
}
99+
100+
window._particles.sort((a, b) => {
101+
const distA = Math.hypot(width - a.x, height - a.y);
102+
const distB = Math.hypot(width - b.x, height - b.y);
103+
return distB - distA;
104+
});
105+
}
106+
107+
const particles = window._particles;
108+
109+
ctx.clearRect(0, 0, width, height);
110+
111+
particles.forEach((p) => {
112+
p.x += p.vx;
113+
p.y += p.vy;
114+
115+
p.trail.push({ x: p.x, y: p.y, alpha: 1 });
116+
if (p.trail.length > 60) p.trail.shift();
117+
118+
p.trail.forEach(point => {
119+
point.alpha *= 0.96;
120+
});
121+
122+
for (let i = 0; i < p.trail.length - 1; i++) {
123+
const a = p.trail[i];
124+
const b = p.trail[i + 1];
125+
ctx.strokeStyle = `oklch(${cssVariable} / ${a.alpha * 0.2})`;
126+
127+
ctx.lineWidth = 2;
128+
ctx.beginPath();
129+
ctx.moveTo(a.x, a.y);
130+
ctx.lineTo(b.x, b.y);
131+
ctx.stroke();
132+
}
133+
134+
ctx.beginPath();
135+
ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2);
136+
ctx.fillStyle = `oklch(${cssVariable} / 0.4)`;
137+
ctx.fill();
138+
139+
if (p.x > width + 20 || p.y > height + 20) {
140+
const { x, y } = getRandomXToRespawn(width, height);
141+
p.x = x;
142+
p.y = y;
143+
p.vx = (0.15 + Math.random() * 0.1) * (width / 500);
144+
p.vy = (0.15 + Math.random() * 0.1) * (height / 500);
145+
p.trail = [];
146+
}
147+
});
148+
}
149+
150+
function getRandomXToRespawn(width: number, height: number): { x: number, y: number } {
151+
let currX = Math.random() * width;
152+
let currY = Math.random() * height;
153+
154+
while (currY > height * 0.2 && currX > width * 0.2 || currX > width * 0.2 && currY > height * 0.2) {
155+
currY = Math.random() * height;
156+
currX = Math.random() * width;
157+
}
158+
159+
return { x: currX, y: currY };
160+
}
161+
62162
function HeroElement(props: Props) {
63163
if (props.search === "") {
64164
return (
65165
<>
66166
<div className="hero back-grid min-h-screen">
167+
<MainAnimationDecorator renderFunction={renderFunc}/>
67168
<div className="hero-content text-center">
68169
<div className="max-w-md flex flex-col items-center justify-center">
69-
<div className="mb-10 avatar online">
170+
<div className="mb-10 avatar">
70171
<div className="w-20 rounded-full">
71172
<img src="https://avatars.githubusercontent.com/u/74710895" alt="Profile Picture"
72173
className="rounded-full w-20"/>

src/assets/GradientOutline.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,19 @@ function notifyListeners() {
1919

2020
function registerHandler() {
2121
if (!handlerRegistered) {
22+
let animationFrameId: number | null = null;
23+
2224
document.addEventListener("pointermove", (e: PointerEvent) => {
2325
mousePos = { x: e.clientX, y: e.clientY };
24-
notifyListeners();
26+
27+
if (animationFrameId === null) {
28+
animationFrameId = requestAnimationFrame(() => {
29+
notifyListeners();
30+
animationFrameId = null;
31+
});
32+
}
2533
});
34+
2635
handlerRegistered = true;
2736
}
2837
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {useEffect} from "react";
2+
3+
interface MainAnimationDecoratorProps {
4+
renderFunction: (ctx: CanvasRenderingContext2D, width: number, height: number) => void;
5+
}
6+
7+
export function MainAnimationDecorator(props: MainAnimationDecoratorProps) {
8+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
9+
10+
useEffect(() => {
11+
if (prefersReducedMotion) return;
12+
const canvas = document.getElementById("main_decorator") as HTMLCanvasElement;
13+
if (canvas) return animateOnCanvas(canvas, props.renderFunction);
14+
}, [prefersReducedMotion, props.renderFunction]);
15+
16+
if (prefersReducedMotion) return <> </>;
17+
18+
19+
return (
20+
<canvas className="w-full h-full" id="main_decorator"></canvas>
21+
);
22+
}
23+
24+
function animateOnCanvas(canvas: HTMLCanvasElement, renderFunction: (ctx: CanvasRenderingContext2D, width: number, height: number) => void) {
25+
const ctx = canvas.getContext("2d");
26+
if (!ctx) return;
27+
28+
let animationFrameId: number;
29+
30+
const resizeCanvas = () => {
31+
canvas.width = canvas.offsetWidth;
32+
canvas.height = canvas.offsetHeight;
33+
};
34+
35+
const render = () => {
36+
if (!ctx) return;
37+
38+
renderFunction(ctx, canvas.width, canvas.height);
39+
40+
animationFrameId = requestAnimationFrame(render);
41+
};
42+
43+
// Initialize canvas size and add resize listener
44+
resizeCanvas();
45+
window.addEventListener("resize", resizeCanvas);
46+
47+
render();
48+
49+
return () => {
50+
cancelAnimationFrame(animationFrameId);
51+
window.removeEventListener("resize", resizeCanvas);
52+
};
53+
}

0 commit comments

Comments
 (0)