refactor: optimize website performance and layout

- Replace ScrollButton with BackToTop component
- Improve theme toggle and scrollbar event handling
- Update layout and index page styling
- Optimize client:load and client:idle directives
- Remove deprecated Heti addon plugin
This commit is contained in:
radishzzz 2025-02-17 16:06:00 +00:00
parent 14f53a979a
commit 47951152d1
13 changed files with 164 additions and 559 deletions

View file

@ -0,0 +1,77 @@
<!-- Sentinel element for scroll detection -->
<div
id="top-sentinel"
class="pointer-events-none absolute left-0 top-0 h-px w-full"
aria-hidden="true"
/>
<button
id="back-to-top-button"
aria-label="Back to top"
class="fixed bottom-8 right-8 h-10 w-10 rounded-full bg-background transition-all duration-300 ease-out"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="m-auto h-60% w-60%"
fill="currentColor"
>
<path d="M18 15l-6-6-6 6" />
</svg>
</button>
<script>
let observer: IntersectionObserver | null = null
let backToTopButton: HTMLButtonElement | null = null
function initBackToTop() {
// Get elements
const sentinel = document.getElementById('top-sentinel')
backToTopButton = document.getElementById('back-to-top-button') as HTMLButtonElement
if (!sentinel || !backToTopButton)
return
// Initialize IntersectionObserver
observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
backToTopButton?.classList.add('opacity-0', 'pointer-events-none', 'translate-y-4')
}
else {
backToTopButton?.classList.remove('opacity-0', 'pointer-events-none', 'translate-y-4')
}
},
{
threshold: 0,
rootMargin: '30% 0% 0% 0%',
},
)
// Observe sentinel
observer.observe(sentinel)
// Add click handler
backToTopButton.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
})
}
function cleanup() {
// Cleanup observer
if (observer) {
observer.disconnect()
observer = null
}
// Remove event listeners
backToTopButton = null
}
// Handle page transitions
document.addEventListener('astro:page-load', initBackToTop)
document.addEventListener('astro:before-swap', cleanup)
</script>

View file

@ -10,6 +10,8 @@
lightbox.destroy()
lightbox = null
}
document.removeEventListener('astro:page-load', createPhotoSwipe)
document.removeEventListener('astro:before-swap', cleanup)
}
function createPhotoSwipe() {

View file

@ -1,91 +0,0 @@
<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>

View file

@ -41,7 +41,6 @@ document.addEventListener('astro:before-swap', () => {
scrollbarsInstance = null
})
document.addEventListener('DOMContentLoaded', initScrollbar)
document.addEventListener('astro:page-load', initScrollbar)
</script>

View file

@ -1,12 +1,29 @@
<button
aria-pressed="false"
aria-label="Theme Toggle Button"
class="absolute right-[calc(9.94vw-1.18rem)] top-[calc(7.3vw+2.6rem)] z-99 aspect-square w-7 c-secondary [@supports(-webkit-touch-callout:none)]:top-[calc(7.3vw+2.2rem)] active:scale-90"
lg="hidden"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="m12 1c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11m0 20c-5.8 0-10.5-4-10.5-9s4.7-9 10.5-9 10.5 4 10.5 9-4.7 9-10.5 9" />
</svg>
</button>
<script>
// Initialize theme toggle button
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
themeToggle.setAttribute('aria-pressed', String(document.documentElement.classList.contains('dark')))
const theme = localStorage.getItem('theme')
document.documentElement.classList.toggle('dark', theme === 'dark')
function switchTheme() {
document.body.removeAttribute('data-restore-theme')
const isDark = document.documentElement.classList.toggle('dark')
themeToggle.setAttribute('aria-pressed', String(isDark))
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
if (themeToggle) {
themeToggle.setAttribute('aria-pressed', String(isDark))
}
localStorage.setItem('theme', isDark ? 'dark' : 'light')
document.dispatchEvent(new Event('theme-changed'))
}
@ -17,7 +34,6 @@ function toggleTheme() {
return
}
// Set temporary transition name for theme toggle
document.documentElement.style.setProperty('view-transition-name', 'theme-transition')
document.documentElement.setAttribute('data-theme-transition', '')
@ -25,7 +41,6 @@ function toggleTheme() {
switchTheme()
})
// Clean up after transition
transition.finished.then(() => {
document.documentElement.style.removeProperty('view-transition-name')
document.documentElement.removeAttribute('data-theme-transition')
@ -36,38 +51,44 @@ function syncTheme() {
document.documentElement.setAttribute('data-restore-theme', 'true')
const theme = localStorage.getItem('theme')
document.documentElement.classList.toggle('dark', theme === 'dark')
themeToggle.setAttribute('aria-pressed', String(theme === 'dark'))
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
if (themeToggle) {
themeToggle.setAttribute('aria-pressed', String(theme === 'dark'))
}
requestAnimationFrame(() => {
document.documentElement.removeAttribute('data-restore-theme')
})
}
document.addEventListener('astro:page-load', () => {
// 事件处理函数
function handleAfterSwap() {
syncTheme()
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
themeToggle.setAttribute('aria-pressed', String(document.documentElement.classList.contains('dark')))
themeToggle.addEventListener('click', toggleTheme)
})
if (themeToggle) {
themeToggle.addEventListener('click', toggleTheme)
}
}
document.addEventListener('astro:after-swap', syncTheme)
window.addEventListener('popstate', syncTheme)
window.addEventListener('pageshow', (event) => {
function handlePageShow(event: PageTransitionEvent) {
if (event.persisted) {
syncTheme()
}
})
</script>
}
<button
aria-pressed="false"
aria-label="Theme Toggle Button"
class="absolute right-[calc(9.94vw-1.18rem)] top-[calc(7.3vw+2.6rem)] z-99 aspect-square w-7 c-secondary [@supports(-webkit-touch-callout:none)]:top-[calc(7.3vw+2.2rem)] active:scale-90"
lg="hidden"
>
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
>
<path d="m12 1c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11m0 20c-5.8 0-10.5-4-10.5-9s4.7-9 10.5-9 10.5 4 10.5 9-4.7 9-10.5 9" />
</svg>
</button>
// 集中管理所有事件监听器
function initThemeEvents() {
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
if (themeToggle) {
themeToggle.addEventListener('click', toggleTheme)
}
document.addEventListener('astro:after-swap', handleAfterSwap)
window.addEventListener('popstate', syncTheme)
window.addEventListener('pageshow', handlePageShow)
}
// 初始化
initThemeEvents()
</script>