mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-17 20:01:33 +02:00
feature: global view animation
- Updated Header component to adjust title margin and changed subtitle from h3 to h2 for better semantic structure. - Refined LanguageSwitcher component to enhance language switching functionality and improved accessibility. - Adjusted Navigation component's margin and link styles for better visual hierarchy. - Enhanced ThemeToggle component with view transition effects for smoother theme changes. - Updated global CSS to support new transition styles for theme toggling. - Bumped rollup version in pnpm-lock.yaml for improved build performance.
This commit is contained in:
parent
b46534419b
commit
bca8b9b1cf
7 changed files with 189 additions and 172 deletions
|
@ -14,17 +14,17 @@ const marginClass = {
|
|||
|
||||
<header>
|
||||
<h1
|
||||
class={`${marginClass} mt--3.2 text-12 c-primary font-bold font-title`}
|
||||
class={`${marginClass} mt--5.2 text-12 c-primary font-bold font-title`}
|
||||
aria-label="Title Tag"
|
||||
>
|
||||
<a href="/">
|
||||
{title}
|
||||
</a>
|
||||
</h1>
|
||||
<h3
|
||||
class="text-5.6 c-primary font-navbar opacity-50"
|
||||
<h2
|
||||
class="text-5.6 c-secondary font-navbar"
|
||||
aria-label="Meta Description"
|
||||
>
|
||||
{subtitle}
|
||||
</h3>
|
||||
</h2>
|
||||
</header>
|
||||
|
|
|
@ -1,60 +1,58 @@
|
|||
---
|
||||
import themeConfig from '@/config'
|
||||
|
||||
// Language array with empty string as default locale
|
||||
const langs = ['', ...themeConfig.global.moreLocale]
|
||||
const currentLocale = themeConfig.global.locale
|
||||
|
||||
function getLanguageDisplayName(code: string) {
|
||||
if (!code)
|
||||
return 'Default'
|
||||
return new Intl.DisplayNames(['en'], { type: 'language' }).of(code) || code
|
||||
}
|
||||
---
|
||||
|
||||
<script is:inline define:vars={{ langs }}>
|
||||
const langSwitch = document.getElementById('language-switcher')
|
||||
|
||||
langSwitch?.addEventListener('click', () => {
|
||||
const { pathname, search, hash } = window.location
|
||||
|
||||
// 获取当前语言和路径段
|
||||
const segments = pathname.split('/').filter(Boolean)
|
||||
const firstSegment = segments[0] || ''
|
||||
const currentLang = langs.includes(firstSegment) ? firstSegment : ''
|
||||
|
||||
// 获取下一个语言
|
||||
const currentIndex = langs.indexOf(currentLang)
|
||||
const nextLang = langs[(currentIndex + 1) % langs.length]
|
||||
|
||||
// 构建新的 URL
|
||||
let newPath
|
||||
if (currentLang) {
|
||||
// 如果当前有语言前缀,替换它
|
||||
segments[0] = nextLang || segments[0]
|
||||
newPath = nextLang ? `/${segments.join('/')}` : `/${segments.slice(1).join('/')}`
|
||||
}
|
||||
else {
|
||||
// 如果当前没有语言前缀
|
||||
newPath = nextLang ? `/${nextLang}${pathname}` : pathname
|
||||
}
|
||||
|
||||
// 确保路径至少是 "/"
|
||||
newPath = newPath || '/'
|
||||
|
||||
// 切换语言
|
||||
window.location.href = `${newPath}${search}${hash}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<button
|
||||
id="language-switcher"
|
||||
class="absolute right-25.6 top-19.4 z-999 aspect-square w-7 c-secondary active:scale-92"
|
||||
aria-label="Language Switch Button"
|
||||
title="Language Switch Button"
|
||||
class="absolute right-25.6 top-20 z-99 aspect-square w-6.6 c-secondary active:scale-92"
|
||||
aria-label="Switch Language"
|
||||
title={`Current Language: ${getLanguageDisplayName(currentLocale)}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-full w-full">
|
||||
<path d="M19 21 12.3 2h-1L4.7 21l-2.5.2v.8h6.3v-.8L5.7 21l2-5.9h7.5l2 5.9-3.3.2v.8h7.9v-.8zM8 14.3l3.4-10.1 3.5 10.1z" fill="currentColor" />
|
||||
</svg>
|
||||
<span class="sr-only">Switch Language</span>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
#language-switcher {
|
||||
transition: transform 0.3s ease;
|
||||
<script is:inline define:vars={{ langs }}>
|
||||
// Move event binding into astro:page-load event
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const langSwitch = document.getElementById('language-switcher')
|
||||
|
||||
langSwitch?.addEventListener('click', () => {
|
||||
const { pathname, search, hash } = window.location
|
||||
const segments = pathname.split('/').filter(Boolean)
|
||||
const firstSegment = segments[0] || ''
|
||||
// Check if first segment is a valid language code
|
||||
const currentLang = langs.includes(firstSegment) ? firstSegment : ''
|
||||
|
||||
// Get next language in rotation (empty string means default locale)
|
||||
const currentIndex = langs.indexOf(currentLang)
|
||||
const nextLang = langs[(currentIndex + 1) % langs.length]
|
||||
|
||||
const newPath = buildNewPath(currentLang, nextLang, segments, pathname) || '/'
|
||||
window.location.href = `${newPath}${search}${hash}`
|
||||
})
|
||||
})
|
||||
|
||||
// Handle path construction for both cases:
|
||||
// 1. Current path has language prefix
|
||||
// 2. Current path has no language prefix
|
||||
function buildNewPath(currentLang, nextLang, segments, pathname) {
|
||||
if (currentLang) {
|
||||
segments[0] = nextLang || segments[0]
|
||||
return nextLang ? `/${segments.join('/')}` : `/${segments.slice(1).join('/')}`
|
||||
}
|
||||
#language-switcher:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
return nextLang ? `/${nextLang}${pathname}` : pathname
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -51,13 +51,13 @@ const isTagActive = isTagPage(currentPath)
|
|||
const isAboutActive = isAboutPage(currentPath)
|
||||
---
|
||||
|
||||
<nav class="mb-19.4 mt-12 text-5.6 text-secondary font-semibold leading-13 font-navbar">
|
||||
<nav class="mb-20 mt-13 text-5.6 text-secondary font-semibold leading-13 font-navbar">
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href={getLocalizedPath('/')}
|
||||
class:list={[
|
||||
isPostActive ? 'font-bold opacity-100' : 'opacity-25',
|
||||
isPostActive ? 'font-bold text-primary' : 'text-secondary',
|
||||
]}
|
||||
>
|
||||
{currentUI.posts}
|
||||
|
@ -67,7 +67,7 @@ const isAboutActive = isAboutPage(currentPath)
|
|||
<a
|
||||
href={getLocalizedPath('/tags')}
|
||||
class:list={[
|
||||
isTagActive ? 'font-bold opacity-100' : 'opacity-25',
|
||||
isTagActive ? 'font-bold text-primary' : 'text-secondary',
|
||||
]}
|
||||
>
|
||||
{currentUI.tags}
|
||||
|
@ -77,7 +77,7 @@ const isAboutActive = isAboutPage(currentPath)
|
|||
<a
|
||||
href={getLocalizedPath('/about')}
|
||||
class:list={[
|
||||
isAboutActive ? 'font-bold opacity-100' : 'opacity-25',
|
||||
isAboutActive ? 'font-bold text-primary' : 'text-secondary',
|
||||
]}
|
||||
>
|
||||
{currentUI.about}
|
||||
|
|
|
@ -18,10 +18,22 @@ function toggleTheme() {
|
|||
switchTheme()
|
||||
return
|
||||
}
|
||||
document.startViewTransition(switchTheme)
|
||||
|
||||
// Set transition name for theme toggle
|
||||
document.documentElement.style.setProperty('view-transition-name', 'theme-transition')
|
||||
|
||||
// Execute transition
|
||||
const transition = document.startViewTransition(() => {
|
||||
switchTheme()
|
||||
}) as any
|
||||
|
||||
// Clean up after transition
|
||||
transition.finished.then(() => {
|
||||
document.documentElement.style.removeProperty('view-transition-name')
|
||||
})
|
||||
}
|
||||
|
||||
// Sync theme state
|
||||
// Sync theme state across page navigation
|
||||
function syncTheme() {
|
||||
document.body.setAttribute('data-restore-theme', 'true')
|
||||
const theme = localStorage.getItem('theme')
|
||||
|
@ -33,7 +45,12 @@ function syncTheme() {
|
|||
}
|
||||
|
||||
// Event listeners
|
||||
themeToggle.addEventListener('click', toggleTheme)
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
||||
themeToggle.setAttribute('aria-pressed', String(document.documentElement.classList.contains('dark')))
|
||||
themeToggle.addEventListener('click', toggleTheme)
|
||||
})
|
||||
|
||||
document.addEventListener('astro:after-swap', syncTheme)
|
||||
window.addEventListener('popstate', syncTheme)
|
||||
window.addEventListener('pageshow', (event) => {
|
||||
|
@ -46,7 +63,7 @@ window.addEventListener('pageshow', (event) => {
|
|||
<button
|
||||
aria-pressed="false"
|
||||
aria-label="Theme Toggle Button"
|
||||
class="absolute right-9.6 top-19.4 z-999 aspect-square w-7 c-secondary active:scale-92"
|
||||
class="absolute right-9.6 top-19.8 z-99 aspect-square w-7 c-secondary active:scale-92"
|
||||
>
|
||||
<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" />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import themeConfig from '@/config'
|
||||
import { ClientRouter } from 'astro:transitions'
|
||||
|
||||
interface Props {
|
||||
postTitle?: string
|
||||
|
@ -35,6 +36,7 @@ const { cdn, commentURL = '', imageHostURL = '', customGoogleAnalyticsURL = '',
|
|||
<meta itemprop="name" content={postTitle || title} />
|
||||
<meta itemprop="image" content={postImage || siteScreenshot} />
|
||||
<meta itemprop="description" content={postDescription || subtitle} />
|
||||
<ClientRouter />
|
||||
|
||||
<!-- Preload -->
|
||||
<link rel="preconnect" href={cdn} />
|
||||
|
|
|
@ -52,16 +52,16 @@ html.dark {
|
|||
html:not(.dark) {
|
||||
--from: 100% 0 0 0;
|
||||
}
|
||||
::view-transition-new(root) {
|
||||
transition: none;
|
||||
animation: reveal 1s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
clip-path: inset(0 0 0 0);
|
||||
z-index: 2;
|
||||
::view-transition-new(theme-transition) {
|
||||
transition: none;
|
||||
animation: reveal 1s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
clip-path: inset(0 0 0 0);
|
||||
z-index: 2;
|
||||
}
|
||||
::view-transition-old(root) {
|
||||
transition: none;
|
||||
animation: none;
|
||||
z-index: -1;
|
||||
::view-transition-old(theme-transition) {
|
||||
transition: none;
|
||||
animation: none;
|
||||
z-index: -1;
|
||||
}
|
||||
@supports not (view-transition-name: none) {
|
||||
body:not([data-restore-theme]) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue