Commit d9e26e65 authored by fisherdaddy's avatar fisherdaddy

feat: add ScrollToTopButton component and styles for improved navigation

parent d562dab4
......@@ -6,6 +6,7 @@ import SEO from './SEO';
import { useTranslation } from '../js/i18n';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
import ScrollToTopButton from './ScrollToTopButton';
const categories = [
{ id: 'all', label: 'All Events' },
......@@ -171,6 +172,9 @@ const AITimeline = () => {
);
})}
</div>
{/* Scroll to top button */}
<ScrollToTopButton />
</div>
</>
);
......
import React from 'react';
import { useScrollToTopButton } from '../hooks/useScrollToTopButton';
import { useTranslation } from '../js/i18n';
/**
* A button that appears when the user scrolls down, allowing them to quickly
* return to the top of the page with a smooth animation.
*/
const ScrollToTopButton = () => {
const { showButton, scrollToTop } = useScrollToTopButton();
const { t } = useTranslation();
return (
<button
className={`scroll-top-button ${showButton ? 'visible' : ''}`}
onClick={scrollToTop}
aria-label={t('common.scrollToTop', '回到顶部')}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 15l-6-6-6 6"/>
</svg>
</button>
);
};
export default ScrollToTopButton;
\ No newline at end of file
import { useState, useEffect } from 'react';
/**
* Custom hook to manage a scroll-to-top button that appears
* when the user scrolls beyond the viewport height
*/
export const useScrollToTopButton = () => {
const [showButton, setShowButton] = useState(false);
useEffect(() => {
const handleScroll = () => {
// Show button when scrolled beyond one viewport height
const scrollThreshold = window.innerHeight * 0.7;
setShowButton(window.scrollY > scrollThreshold);
};
// Add scroll event listener
window.addEventListener('scroll', handleScroll);
// Initial check
handleScroll();
// Clean up event listener
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
return { showButton, scrollToTop };
};
export default useScrollToTopButton;
\ No newline at end of file
......@@ -350,3 +350,62 @@
font-size: 0.75rem;
}
}
/* Scroll to top button */
.scroll-top-button {
position: fixed;
bottom: 30px;
right: 30px;
width: 45px;
height: 45px;
border-radius: 50%;
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
color: white;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
z-index: 100;
transform: translateY(20px);
}
.scroll-top-button.visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
animation: pulse 2s infinite;
}
.scroll-top-button:hover {
transform: translateY(-3px) scale(1.05);
box-shadow: 0 8px 15px rgba(99, 102, 241, 0.4);
animation: none;
background: linear-gradient(135deg, #4F46E5 0%, #3730A3 100%);
}
.scroll-top-button:active {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(99, 102, 241, 0.3);
}
.scroll-top-button svg {
width: 20px;
height: 20px;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.5);
}
70% {
box-shadow: 0 0 0 10px rgba(99, 102, 241, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment