mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 03:32:51 +02:00
feat: add scroll button with dynamic navigation
- Implement ScrollButton component for improved page navigation - Add dynamic scroll direction and visibility based on scroll position - Integrate ScrollButton into main Layout component - Provide smooth scrolling to top or bottom of the page - Use requestAnimationFrame for performance optimization
This commit is contained in:
parent
923d473593
commit
c549814c7e
2 changed files with 94 additions and 1 deletions
91
src/components/ScrollButton.astro
Normal file
91
src/components/ScrollButton.astro
Normal file
|
@ -0,0 +1,91 @@
|
|||
<button
|
||||
id="scroll-button"
|
||||
aria-label="Scroll to bottom"
|
||||
class="fixed bottom-8 right-8 h-10 w-10 rounded-full bg-background p-0 opacity-0 transition-all duration-300"
|
||||
>
|
||||
<svg
|
||||
class="m-auto h-6 w-6 c-primary transition-transform"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<script>
|
||||
const SCROLL_DISPLAY_THRESHOLD = 200
|
||||
const DIRECTION_CHANGE_THRESHOLD = 200
|
||||
const BOTTOM_PROXIMITY_THRESHOLD = 50
|
||||
|
||||
// Initialize scroll button functionality and return cleanup function
|
||||
function initScrollButton(): (() => void) | void {
|
||||
const button = document.getElementById('scroll-button')
|
||||
if (!button)
|
||||
return
|
||||
|
||||
const buttonElement = button as HTMLButtonElement
|
||||
let isScrollingDown = true
|
||||
let lastScrollPosition = window.scrollY
|
||||
let scrollTimeout: number | null = null
|
||||
|
||||
function updateButton() {
|
||||
const currentScrollPosition = window.scrollY
|
||||
const scrollDifference = currentScrollPosition - lastScrollPosition
|
||||
const maxScroll = document.documentElement.scrollHeight - window.innerHeight
|
||||
const isNearBottom = maxScroll - currentScrollPosition < BOTTOM_PROXIMITY_THRESHOLD
|
||||
|
||||
// Update button direction and behavior when scroll direction changes or near bottom
|
||||
if (Math.abs(scrollDifference) > DIRECTION_CHANGE_THRESHOLD || isNearBottom) {
|
||||
isScrollingDown = isNearBottom ? false : scrollDifference > 0
|
||||
lastScrollPosition = currentScrollPosition
|
||||
|
||||
const svg = buttonElement.querySelector('svg')
|
||||
if (svg instanceof SVGElement) {
|
||||
svg.style.transform = isScrollingDown ? 'rotate(-90deg)' : 'rotate(90deg)'
|
||||
}
|
||||
|
||||
buttonElement.setAttribute('aria-label', isScrollingDown ? 'Scroll to bottom' : 'Scroll to top')
|
||||
buttonElement.onclick = () => {
|
||||
window.scrollTo({
|
||||
top: isScrollingDown ? maxScroll : 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const shouldShow = currentScrollPosition > SCROLL_DISPLAY_THRESHOLD
|
||||
buttonElement.classList.toggle('opacity-100', shouldShow)
|
||||
buttonElement.classList.toggle('invisible', !shouldShow)
|
||||
}
|
||||
|
||||
// Handle scroll events with requestAnimationFrame for performance
|
||||
function scrollHandler() {
|
||||
if (scrollTimeout) {
|
||||
window.cancelAnimationFrame(scrollTimeout)
|
||||
}
|
||||
scrollTimeout = window.requestAnimationFrame(updateButton)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', scrollHandler, { passive: true })
|
||||
updateButton()
|
||||
|
||||
// Return cleanup function to remove event listeners
|
||||
return () => {
|
||||
window.removeEventListener('scroll', scrollHandler)
|
||||
if (scrollTimeout) {
|
||||
window.cancelAnimationFrame(scrollTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cleanup: (() => void) | null = null
|
||||
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
cleanup?.()
|
||||
cleanup = initScrollButton() || null
|
||||
})
|
||||
|
||||
document.addEventListener('astro:before-swap', () => cleanup?.())
|
||||
</script>
|
|
@ -6,6 +6,7 @@ import MobileHeader from '@/components/MobileHeader.astro'
|
|||
import Navigation from '@/components/Navbar.astro'
|
||||
import PhotoSwipe from '@/components/PhotoSwipe.astro'
|
||||
import Scrollbar from '@/components/Scrollbar.astro'
|
||||
import ScrollButton from '@/components/ScrollButton.astro'
|
||||
import ThemeToggle from '@/components/ThemeToggle.astro'
|
||||
import themeConfig from '@/config'
|
||||
import Head from '@/layouts/Head.astro'
|
||||
|
@ -27,7 +28,7 @@ const { isHome, isPost } = getPagePath(Astro.url.pathname);
|
|||
|
||||
<html
|
||||
lang={Astro.currentLocale || 'en-US'}
|
||||
class:list={[fontStyle, isPost && 'scroll-smooth ']}
|
||||
class:list={[fontStyle]}
|
||||
data-overlayscrollbars-initialize
|
||||
>
|
||||
<Head {postTitle} {postDescription} {postImage} />
|
||||
|
@ -53,5 +54,6 @@ const { isHome, isPost } = getPagePath(Astro.url.pathname);
|
|||
<LanguageSwitcher />
|
||||
<Scrollbar />
|
||||
<PhotoSwipe />
|
||||
<ScrollButton />
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue