c9cf644ac5
- 添加 build-and-push.sh 脚本用于自动化构建和推送 Docker 镜像 - 更新 .dockerignore 排除 pnpm-workspace.yaml - 更新项目依赖和配置文件
87 lines
3.1 KiB
Vue
Executable File
87 lines
3.1 KiB
Vue
Executable File
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
|
import gsap from 'gsap'
|
|
import { useAppStore } from '~/stores/app'
|
|
import { usePageScrollLock } from '~/composables/usePageScrollLock'
|
|
|
|
const loaderRef = ref<HTMLElement | null>(null)
|
|
const loaderBarRef = ref<HTMLElement | null>(null)
|
|
const appStore = useAppStore()
|
|
|
|
// 加载期间锁定页面滚动(注意:Pinia 的 state 是自动解包的,必须用 computed 包装才能保持响应式)
|
|
usePageScrollLock(computed(() => appStore.isAppLoading), { className: 'is-scroll-locked' })
|
|
|
|
let loaderTween: gsap.core.Tween | null = null
|
|
let removeLoadListener: (() => void) | null = null
|
|
let introStartTimer: ReturnType<typeof window.setTimeout> | null = null
|
|
|
|
onMounted(() => {
|
|
if (!loaderRef.value || !loaderBarRef.value) return
|
|
|
|
// 1. 初始化 GSAP 样式状态
|
|
gsap.set(loaderRef.value, { display: 'grid', yPercent: 0, autoAlpha: 1 })
|
|
gsap.set(loaderBarRef.value, { scaleX: 0.18, transformOrigin: 'left center' })
|
|
|
|
// 2. 进度条往复加载动画
|
|
loaderTween = gsap.to(loaderBarRef.value, {
|
|
scaleX: 0.82,
|
|
duration: 1.2,
|
|
ease: 'power1.inOut',
|
|
repeat: -1,
|
|
yoyo: true,
|
|
})
|
|
|
|
const loaderStartedAt = window.performance.now()
|
|
const minLoaderDuration = 1200 // 保证加载动画至少播放 1.2 秒,避免一闪而过
|
|
|
|
const finishLoading = () => {
|
|
removeLoadListener?.()
|
|
if (introStartTimer) clearTimeout(introStartTimer)
|
|
|
|
// 计算还需要等待多久才能达到最小加载时间
|
|
const remainingTime = Math.max(0, minLoaderDuration - (window.performance.now() - loaderStartedAt))
|
|
|
|
introStartTimer = setTimeout(() => {
|
|
// 停止往复动画
|
|
loaderTween?.kill()
|
|
|
|
// 3. 执行加载器退场动画
|
|
gsap.timeline({
|
|
onComplete: () => {
|
|
gsap.set(loaderRef.value, { display: 'none' })
|
|
appStore.setAppLoading(false)
|
|
window.dispatchEvent(new CustomEvent('app-loading-done'))
|
|
}
|
|
})
|
|
.to(loaderBarRef.value, { scaleX: 1, duration: 0.25, ease: 'power2.out' })
|
|
.to(loaderRef.value, { yPercent: -100, duration: 0.75, ease: 'power4.inOut' })
|
|
}, remainingTime)
|
|
}
|
|
|
|
// 监听浏览器 window.onload 事件以判断页面资源是否加载完毕
|
|
if (document.readyState === 'complete') {
|
|
finishLoading()
|
|
} else {
|
|
window.addEventListener('load', finishLoading, { once: true })
|
|
removeLoadListener = () => window.removeEventListener('load', finishLoading)
|
|
}
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
loaderTween?.kill()
|
|
if (introStartTimer) clearTimeout(introStartTimer)
|
|
removeLoadListener?.()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="loaderRef" class="fixed inset-0 z-[90] grid place-items-center bg-black text-white will-change-transform" aria-hidden="true">
|
|
<div class="w-[min(220px,calc(100vw-48px))] text-xs uppercase tracking-[0.18em]">
|
|
<p class="m-0 mb-[14px]">Loading</p>
|
|
<span class="block h-px w-full overflow-hidden bg-white/20">
|
|
<span ref="loaderBarRef" class="block h-full w-full origin-left scale-x-0 bg-white"></span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|