Skip to content

Commit 74af094

Browse files
return to top
1 parent edcc3ce commit 74af094

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Inter } from 'next/font/google';
33
import { Analytics } from '@vercel/analytics/next';
44
import Navbar from './components/navbar';
55
import BrandParticles from '@/components/BrandParticles';
6+
import ReturnToTop from '@/components/ReturnToTop';
67
import type { Metadata } from 'next';
78

89
const inter = Inter({ subsets: ['latin'] });
@@ -69,6 +70,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
6970
<BrandParticles />
7071
<Navbar />
7172
<div className="pt-24 sm:pt-28 relative z-10">{children}</div>
73+
<ReturnToTop />
7274
<Analytics />
7375
</body>
7476
</html>

components/ReturnToTop.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
import { motion, AnimatePresence } from 'framer-motion';
5+
import { ChevronUp } from 'lucide-react';
6+
7+
export default function ReturnToTop() {
8+
const [isVisible, setIsVisible] = useState(false);
9+
10+
useEffect(() => {
11+
const toggleVisibility = () => {
12+
// Show button when user is near the bottom of the page
13+
const scrollHeight = document.documentElement.scrollHeight;
14+
const scrollTop = window.scrollY;
15+
const clientHeight = window.innerHeight;
16+
17+
// Show when user is within 300px of the bottom
18+
if (scrollHeight - (scrollTop + clientHeight) < 300) {
19+
setIsVisible(true);
20+
} else {
21+
setIsVisible(false);
22+
}
23+
};
24+
25+
window.addEventListener('scroll', toggleVisibility);
26+
return () => window.removeEventListener('scroll', toggleVisibility);
27+
}, []);
28+
29+
const scrollToTop = () => {
30+
window.scrollTo({
31+
top: 0,
32+
behavior: 'smooth',
33+
});
34+
};
35+
36+
return (
37+
<AnimatePresence>
38+
{isVisible && (
39+
<motion.button
40+
initial={{ opacity: 0, y: 20 }}
41+
animate={{ opacity: 1, y: 0 }}
42+
exit={{ opacity: 0, y: 20 }}
43+
transition={{ duration: 0.3 }}
44+
onClick={scrollToTop}
45+
className="fixed bottom-8 right-8 p-3 bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-600 hover:to-blue-600 text-white rounded-full shadow-lg hover:shadow-xl transition-shadow duration-300 z-50 flex items-center justify-center"
46+
aria-label="Return to top"
47+
>
48+
<ChevronUp size={24} />
49+
</motion.button>
50+
)}
51+
</AnimatePresence>
52+
);
53+
}

0 commit comments

Comments
 (0)