Skip to content

Commit 469352e

Browse files
committed
add ascii animation
1 parent 8c6973e commit 469352e

3 files changed

Lines changed: 119 additions & 2 deletions

File tree

app/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Link from "next/link";
33
import { formatDate } from "@/lib/utils";
44

55
import { getPosts } from "./posts/page";
6+
import { AsciiAnimation } from "@/components/ascii-animation";
67

78
export default async function Home() {
89
const blogPosts = await getPosts();
@@ -14,7 +15,7 @@ export default async function Home() {
1415

1516
return (
1617
<div className="min-h-screen bg-white dark:bg-zinc-950">
17-
<main className="container mx-auto px-4 py-12">
18+
<main className="container mx-auto px-4 py-12 z-10 relative">
1819
{/* Hero Section */}
1920
{/* <section className="mb-16 border-b border-zinc-200 dark:border-zinc-800 pb-16">
2021
<h1 className="mb-6 text-zinc-900 dark:text-zinc-50">{siteConfig.name}</h1>
@@ -141,6 +142,7 @@ export default async function Home() {
141142
</div>
142143
</section>
143144
</main>
145+
<AsciiAnimation className="fixed top-0 z-0" />
144146
</div>
145147
);
146148
}

components/ascii-animation.tsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"use client";
2+
3+
import { cn } from "@/lib/utils";
4+
import { useEffect, useRef, useState } from "react";
5+
6+
export function AsciiAnimation({ className }: { className?: string }) {
7+
const preRef = useRef<HTMLPreElement>(null);
8+
const [mousePosition, setMousePosition] = useState({ x: 0.5, y: 0 });
9+
const animationFrameRef = useRef(0);
10+
const timeRef = useRef(0);
11+
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
12+
13+
// Handle resize and calculate dimensions
14+
useEffect(() => {
15+
const handleResize = () => {
16+
if (preRef.current) {
17+
const style = window.getComputedStyle(preRef.current);
18+
const fontWidth = Number.parseFloat(style.fontSize) * 0.6;
19+
const fontHeight = Number.parseFloat(style.lineHeight) || Number.parseFloat(style.fontSize) * 1.2;
20+
21+
// Limit dimensions for better performance
22+
const width = Math.min(150, Math.floor(preRef.current.clientWidth / fontWidth));
23+
const height = Math.min(80, Math.floor(preRef.current.clientHeight / fontHeight));
24+
25+
setDimensions({ width, height });
26+
}
27+
};
28+
29+
handleResize();
30+
window.addEventListener("resize", handleResize);
31+
32+
return () => {
33+
window.removeEventListener("resize", handleResize);
34+
cancelAnimationFrame(animationFrameRef.current);
35+
};
36+
}, []);
37+
38+
// Handle mouse movement
39+
useEffect(() => {
40+
const handleMouseMove = (e: MouseEvent) => {
41+
if (preRef.current) {
42+
const rect = preRef.current.getBoundingClientRect();
43+
setMousePosition({
44+
x: (e.clientX - rect.left) / rect.width,
45+
// y: (e.clientY - rect.top) / rect.height,
46+
y: 0,
47+
});
48+
}
49+
};
50+
51+
window.addEventListener("mousemove", handleMouseMove);
52+
return () => window.removeEventListener("mousemove", handleMouseMove);
53+
}, []);
54+
55+
// Animation loop
56+
useEffect(() => {
57+
if (dimensions.width === 0 || dimensions.height === 0) return;
58+
59+
const chars = " .,:;+*?%S#@";
60+
61+
const animate = () => {
62+
timeRef.current += 0.03;
63+
64+
if (!preRef.current) return;
65+
66+
let result = "";
67+
68+
for (let y = 0; y < dimensions.height; y++) {
69+
for (let x = 0; x < dimensions.width; x++) {
70+
// Normalized coordinates
71+
const nx = x / dimensions.width - 0.5;
72+
const ny = y / dimensions.height - 0.5;
73+
74+
// Distance from mouse position (normalized)
75+
const dx = nx - (mousePosition.x - 0.5);
76+
const dy = ny - (mousePosition.y - 0.5);
77+
const dist = Math.sqrt(dx * dx + dy * dy);
78+
79+
// Wave pattern
80+
const wave = Math.sin(dist * 10 - timeRef.current * 2) * 0.5 + 0.5;
81+
82+
// Choose character based on wave value
83+
const charIndex = Math.floor(wave * (chars.length - 1));
84+
result += chars[charIndex];
85+
}
86+
result += "\n";
87+
}
88+
89+
preRef.current.textContent = result;
90+
animationFrameRef.current = requestAnimationFrame(animate);
91+
};
92+
93+
// Start animation
94+
animationFrameRef.current = requestAnimationFrame(animate);
95+
96+
// Cleanup
97+
return () => {
98+
cancelAnimationFrame(animationFrameRef.current);
99+
};
100+
}, [dimensions, mousePosition]);
101+
102+
return (
103+
<div className={cn("flex items-center justify-center w-full h-screen mx-auto", className)}>
104+
<pre
105+
ref={preRef}
106+
className="w-full h-full p-0 m-0 overflow-hidden leading-[10px] font-mono opacity-[5%]"
107+
style={{
108+
fontSize: "1.5rem",
109+
fontFamily: "monospace",
110+
userSelect: "none",
111+
}}
112+
/>
113+
</div>
114+
);
115+
}

components/site-footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { siteConfig } from "@/lib/site-config";
33

44
export function SiteFooter() {
55
return (
6-
<footer className="border-t border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-950">
6+
<footer className="border-t border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-950 z-10">
77
<div className="container mx-auto px-4 py-8">
88
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
99
<div>

0 commit comments

Comments
 (0)