45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import gsap from 'gsap';
|
|
|
|
const BackToTop: React.FC = () => {
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const toggleVisibility = () => {
|
|
if (window.scrollY > 500) {
|
|
setIsVisible(true);
|
|
} else {
|
|
setIsVisible(false);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', toggleVisibility);
|
|
return () => window.removeEventListener('scroll', toggleVisibility);
|
|
}, []);
|
|
|
|
const scrollToTop = () => {
|
|
gsap.to(window, { duration: 1.2, scrollTo: 0, ease: "power3.inOut" });
|
|
};
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isVisible && (
|
|
<motion.button
|
|
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.8, y: 20 }}
|
|
whileHover={{ scale: 1.1, backgroundColor: "#3b82f6" }}
|
|
whileTap={{ scale: 0.9 }}
|
|
onClick={scrollToTop}
|
|
className="fixed bottom-8 right-8 z-50 w-12 h-12 flex items-center justify-center rounded-full bg-black dark:bg-white text-white dark:text-black shadow-lg border border-gray-700 dark:border-gray-200 transition-colors"
|
|
aria-label="Back to top"
|
|
>
|
|
<span className="material-symbols-outlined text-2xl">arrow_upward</span>
|
|
</motion.button>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
};
|
|
|
|
export default BackToTop; |