66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { motion, AnimatePresence } from 'framer-motion';
|
||
|
||
export default function ScrollToTop() {
|
||
const [showScrollTop, setShowScrollTop] = useState(false);
|
||
|
||
useEffect(() => {
|
||
let ticking = false;
|
||
|
||
const handleScroll = () => {
|
||
if (!ticking) {
|
||
window.requestAnimationFrame(() => {
|
||
const currentScrollY = window.scrollY;
|
||
// 显示返回顶部按钮(滚动超过300px)
|
||
setShowScrollTop(currentScrollY > 300);
|
||
ticking = false;
|
||
});
|
||
ticking = true;
|
||
}
|
||
};
|
||
|
||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||
return () => window.removeEventListener('scroll', handleScroll);
|
||
}, []);
|
||
|
||
const scrollToTop = () => {
|
||
window.scrollTo({
|
||
top: 0,
|
||
behavior: 'smooth'
|
||
});
|
||
};
|
||
|
||
return (
|
||
<AnimatePresence>
|
||
{showScrollTop && (
|
||
<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 }}
|
||
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||
onClick={scrollToTop}
|
||
className="fixed bottom-6 right-6 w-12 h-12 bg-gradient-to-br from-orange-400/70 to-amber-300/70 hover:from-orange-600/70 hover:to-orange-700/70 text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center z-40 group"
|
||
whileHover={{ scale: 1.1, y: -2 }}
|
||
whileTap={{ scale: 0.9 }}
|
||
>
|
||
<svg
|
||
className="w-5 h-5 transition-transform duration-200 group-hover:-translate-y-0.5"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth={2}
|
||
d="M5 10l7-7m0 0l7 7m-7-7v18"
|
||
/>
|
||
</svg>
|
||
</motion.button>
|
||
)}
|
||
</AnimatePresence>
|
||
);
|
||
}
|