diff --git a/client/src/composables/useScrollAnimation.ts b/client/src/composables/useScrollAnimation.ts index 5a13326..ee4d409 100644 --- a/client/src/composables/useScrollAnimation.ts +++ b/client/src/composables/useScrollAnimation.ts @@ -1,9 +1,17 @@ -import { onMounted, onUnmounted } from 'vue' +import { onMounted, onUnmounted, nextTick } from 'vue' export function useScrollAnimation() { let observer: IntersectionObserver | null = null - const observe = () => { + const initObserver = async () => { + // Wait for DOM updates + await nextTick() + + // Disconnect existing observer if any + if (observer) { + observer.disconnect() + } + observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { @@ -17,19 +25,28 @@ export function useScrollAnimation() { rootMargin: '50px' // Pre-load slightly before element comes into view }) - document.querySelectorAll('.animate-slide-down, .animate-reveal').forEach(el => { - observer?.observe(el) - }) + const elements = document.querySelectorAll('.animate-slide-down, .animate-reveal') + if (elements.length === 0) { + // Retry once if no elements found (e.g. data loaded but DOM not quite ready) + setTimeout(() => { + document.querySelectorAll('.animate-slide-down, .animate-reveal').forEach(el => { + observer?.observe(el) + }) + }, 100) + } else { + elements.forEach(el => { + observer?.observe(el) + }) + } } - onMounted(() => { - // Small delay to ensure DOM is ready - setTimeout(observe, 100) - }) - onUnmounted(() => { if (observer) { observer.disconnect() } }) + + return { + initObserver + } } diff --git a/client/src/pages/About.vue b/client/src/pages/About.vue index b71f3ec..797bd2b 100644 --- a/client/src/pages/About.vue +++ b/client/src/pages/About.vue @@ -111,6 +111,8 @@ const profile = ref({ const loading = ref(false) const error = ref('') +const { initObserver } = useScrollAnimation() + // 模拟API调用,实际项目中应替换为真实API const fetchProfileData = async () => { loading.value = true @@ -134,6 +136,8 @@ const fetchProfileData = async () => { }, techStack: ['Vue 3', 'React', 'TypeScript', 'Three.js', 'Golang', 'Tailwind CSS', 'Vite', 'Gin'] } + // 初始化动画 + initObserver() } catch (err) { console.error('Error fetching profile data:', err) error.value = '获取个人资料失败,请稍后重试' @@ -143,14 +147,8 @@ const fetchProfileData = async () => { } } -onBeforeMount(() => { - fetchProfileData() -}) - onMounted(() => { - // 启用滚动动画 - useScrollAnimation() - + fetchProfileData() // 初始化Lucide图标 if (window.lucide) { window.lucide.createIcons() diff --git a/client/src/pages/Blog.vue b/client/src/pages/Blog.vue index 490f21b..8ed65e5 100644 --- a/client/src/pages/Blog.vue +++ b/client/src/pages/Blog.vue @@ -52,12 +52,16 @@ const blogPosts = ref([]) const loading = ref(false) const error = ref('') +const { initObserver } = useScrollAnimation() + const fetchBlogPosts = async () => { loading.value = true error.value = '' try { const posts = await fetchPosts() blogPosts.value = posts + // 初始化动画 - 在数据加载完成后 + initObserver() } catch (err) { console.error('Error fetching blog posts:', err) error.value = '获取博客文章失败,请稍后重试' @@ -66,14 +70,8 @@ const fetchBlogPosts = async () => { } } -onBeforeMount(() => { - fetchBlogPosts() -}) - onMounted(() => { - // 启用滚动动画 - useScrollAnimation() - + fetchBlogPosts() // 初始化Lucide图标 if (window.lucide) { window.lucide.createIcons() diff --git a/client/src/pages/BlogDetail.vue b/client/src/pages/BlogDetail.vue index 7221937..45bb4a5 100644 --- a/client/src/pages/BlogDetail.vue +++ b/client/src/pages/BlogDetail.vue @@ -81,6 +81,8 @@ const post = ref({ const loading = ref(false) const error = ref('') +const { initObserver } = useScrollAnimation() + const fetchBlogDetail = async () => { loading.value = true error.value = '' @@ -88,6 +90,8 @@ const fetchBlogDetail = async () => { const postData = await fetchPost(postId) if (postData) { post.value = postData + // 初始化动画 - 在数据加载完成后 + initObserver() } else { error.value = '未找到该文章' } @@ -99,14 +103,8 @@ const fetchBlogDetail = async () => { } } -onBeforeMount(() => { - fetchBlogDetail() -}) - onMounted(() => { - // 启用滚动动画 - useScrollAnimation() - + fetchBlogDetail() // 初始化Lucide图标 if (window.lucide) { window.lucide.createIcons() diff --git a/client/src/pages/Home.vue b/client/src/pages/Home.vue index 8cf1962..775f564 100644 --- a/client/src/pages/Home.vue +++ b/client/src/pages/Home.vue @@ -108,6 +108,8 @@ import { ref, onMounted, onBeforeMount } from 'vue' import { fetchWorks, fetchPosts, Work, Post } from '../services/api' import { useScrollAnimation } from '../composables/useScrollAnimation' +const { initObserver } = useScrollAnimation() + const latestWork = ref({ id: 'nova', title: 'Nova 交易平台', @@ -137,15 +139,16 @@ const fetchHomeData = async () => { loading.value = true error.value = '' try { - // 获取最新作品 - const works = await fetchWorks() - if (works.length > 0) { + const [works, posts] = await Promise.all([ + fetchWorks(), + fetchPosts() + ]) + + if (works && works.length > 0) { latestWork.value = works[0] } - // 获取精选文章 - const posts = await fetchPosts() - if (posts.length > 0) { + if (posts && posts.length > 0) { featuredPost.value = posts[0] } } catch (err) { @@ -153,15 +156,12 @@ const fetchHomeData = async () => { error.value = '获取首页数据失败' } finally { loading.value = false + // 启用滚动动画 + initObserver() } } -onBeforeMount(() => { +onMounted(() => { fetchHomeData() }) - -onMounted(() => { - // 启用滚动动画 - useScrollAnimation() -}) \ No newline at end of file diff --git a/client/src/pages/Services.vue b/client/src/pages/Services.vue index 26a3600..a1ee6dc 100644 --- a/client/src/pages/Services.vue +++ b/client/src/pages/Services.vue @@ -246,9 +246,11 @@ onBeforeMount(() => { fetchServicesData() }) +const { initObserver } = useScrollAnimation() + onMounted(() => { // 启用滚动动画 - useScrollAnimation() + initObserver() // 初始化Lucide图标 if (window.lucide) { diff --git a/client/src/pages/Snippets.vue b/client/src/pages/Snippets.vue index 45402da..cba77a8 100644 --- a/client/src/pages/Snippets.vue +++ b/client/src/pages/Snippets.vue @@ -52,12 +52,16 @@ const snippets = ref([]) const loading = ref(false) const error = ref('') +const { initObserver } = useScrollAnimation() + const fetchSnippetsData = async () => { loading.value = true error.value = '' try { const snippetsData = await fetchSnippets() snippets.value = snippetsData + // 初始化动画 - 在数据加载完成后 + initObserver() } catch (err) { console.error('Error fetching snippets:', err) error.value = '获取代码片段失败,请稍后重试' @@ -111,6 +115,8 @@ export const useMousePosition = () => { type: 'animate' } ] + // 静态数据加载后也要初始化动画 + initObserver() } finally { loading.value = false } @@ -136,14 +142,8 @@ const openSnippet = (snippet: any) => { } } -onBeforeMount(() => { - fetchSnippetsData() -}) - onMounted(() => { - // 启用滚动动画 - useScrollAnimation() - + fetchSnippetsData() // 初始化Lucide图标 if (window.lucide) { window.lucide.createIcons() diff --git a/client/src/pages/WorkDetail.vue b/client/src/pages/WorkDetail.vue index 9c32735..1ab20ca 100644 --- a/client/src/pages/WorkDetail.vue +++ b/client/src/pages/WorkDetail.vue @@ -124,6 +124,8 @@ const work = ref({ const loading = ref(true) const error = ref('') +const { initObserver } = useScrollAnimation() + const updateScrollProgress = () => { const scrollTop = window.scrollY const docHeight = document.documentElement.scrollHeight @@ -142,6 +144,8 @@ const fetchWorkDetails = async () => { const workData = await fetchWork(workId) if (workData) { work.value = workData + // 初始化动画 - 在数据加载完成后 + initObserver() } else { error.value = '未找到该作品' } @@ -154,9 +158,6 @@ const fetchWorkDetails = async () => { } onMounted(() => { - // 启用滚动动画 - useScrollAnimation() - fetchWorkDetails() // 初始化Lucide图标 if (window.lucide) { diff --git a/client/src/pages/Works.vue b/client/src/pages/Works.vue index f541732..ad4b4ff 100644 --- a/client/src/pages/Works.vue +++ b/client/src/pages/Works.vue @@ -90,11 +90,15 @@ const works = ref([]) const loading = ref(false) const error = ref('') +const { initObserver } = useScrollAnimation() + const fetchWorksData = async () => { loading.value = true error.value = '' try { works.value = await fetchWorks() + // 初始化动画 - 在数据加载完成后 + initObserver() } catch (err) { error.value = '获取作品失败,请稍后重试' console.error('Error fetching works:', err) @@ -104,9 +108,6 @@ const fetchWorksData = async () => { } onMounted(() => { - // 启用滚动动画 - useScrollAnimation() - fetchWorksData() // 初始化Lucide图标 if (window.lucide) {